Add source

This commit is contained in:
seven332 committed 2015-01-27 20:10:45 +08:00
1 parent 6004602ab3
commit fe8021a512
29 files changed
+3313 -8

No files matched your search

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.ehviewer.client;
import com.hippo.util.AssertUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DetailUrlParser {
public int gid;
public String token;
public void parser(String url) throws Exception {
AssertUtils.assertNotNull("Url is null when parse detail url", url);
Pattern p = Pattern.compile("/(\\d+)/(\\w+)");
Matcher m = p.matcher(url);
if (m.find()) {
gid = Integer.parseInt(m.group(1));
token = m.group(2);
} else {
throw new EhException("Can not parse detail url");
}
}
}
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.ehviewer.client;
import android.os.AsyncTask;
import com.hippo.ehviewer.data.GalleryInfo;
import com.hippo.ehviewer.network.EhHttpHelper;
import com.hippo.util.Utils;
public class EhClient {
@SuppressWarnings("unused")
private static final String TAG = EhClient.class.getSimpleName();
public static final int SOURCE_G = 0x0;
public static final int SOURCE_EX = 0x1;
public static final int SOURCE_LOFI = 0x2;
public static final String API_G = "http://g.e-hentai.org/api.php";
public static final String API_EX = "http://exhentai.org/api.php";
public static final long APIUID = 1363542;
public static final String APIKEY = "f4b5407ab1727b9d08d7";
private static final String LOGIN_URL =
"http://forums.e-hentai.org/index.php?act=Login&CODE=01";
private static final String FORUMS_URL = "http://forums.e-hentai.org/index.php";
public static final String HEADER_G = "http://g.e-hentai.org/";
public static final String HEADER_EX = "http://exhentai.org/";
public static final String HEADER_LOFI = "http://lofi.e-hentai.org/";
public static final String API_EHVIEWER = "http://www.ehviewer.com/API";
/**
* Get gallery list
*
* @param source the source, one of {@link #SOURCE_G}, {@link #SOURCE_EX} and
* {@link #SOURCE_LOFI}
* @param url the url to get gallery list
* @return a array with {@code GalleryInfo[]} gallery info array and
* {@code int} pages number
*/
public Object[] doGetGalleryList(int source, String url) throws Exception {
EhHttpHelper ehh = new EhHttpHelper();
String body = ehh.get(url);
ListParser parser = new ListParser();
parser.parse(body, source);
return new Object[]{parser.giArray, parser.pageNum};
}
private void doBgJob(BgJobHelper bjh) {
Utils.execute(false, new AsyncTask<Object, Void, Object[]>() {
@Override
protected Object[] doInBackground(Object... params) {
BgJobHelper bjh = (BgJobHelper) params[0];
return new Object[]{bjh.doInBackground(), bjh};
}
@Override
protected void onPostExecute(Object[] resultPackage) {
Object result = resultPackage[0];
BgJobHelper bjh = (BgJobHelper) resultPackage[1];
bjh.onPostExecute(result);
}
}, bjh);
}
private static interface BgJobHelper {
public Object doInBackground();
public void onPostExecute(Object result);
}
public interface OnGetGalleryListListener {
public void onSuccess(GalleryInfo[] glArray, int pageNum);
public void onFailure(Exception e);
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* 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
* 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 com.hippo.ehviewer.client;
public class EhException extends Exception {
public EhException(String detailMessage) {
super(detailMessage);
}
}
@@ -0,0 +1,243 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.ehviewer.client;
import com.hippo.ehviewer.data.GalleryInfo;
import com.hippo.ehviewer.data.LofiGalleryInfo;
import com.hippo.ehviewer.util.EhUtils;
import com.hippo.util.AssertUtils;
import com.hippo.util.NotEqualsException;
import com.hippo.util.Utils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ListParser {
private static final String PARSE_MESSAGE = "Gallery list parser error";
private static final int DEFAULT_LIST_SIZE = 25;
public static final int CURRENT_PAGE_IS_LAST = -1;
/**
* If NOT FOUND, pageNum is 0.<br>
* For lofi, we can not get pages number,
* so pageNum is {@link java.lang.Integer#MAX_VALUE} when it is not the last page,
* pageNum is {@link #CURRENT_PAGE_IS_LAST} when it is the last page
*/
public int pageNum;
public GalleryInfo[] giArray;
private void parse(String body) throws EhException {
Pattern p;
Matcher m;
// Get page number
p = Pattern.compile("<a[^<>]+>([\\d]+)</a></td><td[^<>]+>(?:<a[^<>]+>)?&");
m = p.matcher(body);
if (m.find()) {
pageNum = Integer.parseInt(m.group(1));
} else if (body.contains("No hits found</p>")) {
pageNum = 0;
} else {
// Can not get page number
throw new EhException(PARSE_MESSAGE);
}
// Get gallery
List<GalleryInfo> giList = new ArrayList<>(DEFAULT_LIST_SIZE);
p = Pattern.compile("<td class=\"itdc\">(?:<a.+?>)?<img.+?alt=\"(.+?)\".+?/>(?:</a>)?</td>" // category
+ "<td.+?>(.+?)</td>" // posted
+ "<td.+?><div.+?><div.+?height:(\\d+)px; width:(\\d+)px\">"
+ "(?:<img.+?src=\"(.+?)\".+?alt=\"(.+?)\" style.+?/>"
+ "|init~([^<>\"~]+~[^<>\"~]+)~([^<>]+))" // thumb and title
+ "</div>"
+ ".+?"
+ "<div class=\"it5\"><a href=\"([^<>\"]+)\"[^<>]+>(.+?)</a></div>" // url and title
+ ".+?"
+ "<div class=\"ir it4r\" style=\"([^<>\"]+)\">" // rating
+ ".+?"
+ "<td class=\"itu\"><div><a.+?>(.+?)</a>"); // uploader
m = p.matcher(body);
while (m.find()) {
GalleryInfo gi = new GalleryInfo();
gi.category = EhUtils.getCategory(m.group(1));
gi.posted = m.group(2);
gi.thumbHeight = Integer.parseInt(m.group(3));
gi.thumbWidth = Integer.parseInt(m.group(4));
if (m.group(5) == null) {
gi.thumb = Utils.unescapeXml("http://"
+ m.group(7).replace('~', '/'));
gi.title = Utils.unescapeXml(m.group(8));
} else {
gi.thumb = Utils.unescapeXml(m.group(5));
gi.title = Utils.unescapeXml(m.group(6));
}
Pattern pattern = Pattern
.compile("/(\\d+)/(\\w+)");
Matcher matcher = pattern.matcher(m.group(9));
if (matcher.find()) {
gi.gid = Integer.parseInt(matcher.group(1));
gi.token = matcher.group(2);
} else {
continue;
}
gi.rating = Utils.parseFloatSafely(getRate(m.group(11)), Float.NaN);
gi.uploader = m.group(12);
gi.generateSLang();
giList.add(gi);
}
if (giList.size() == 0) {
throw new EhException(PARSE_MESSAGE);
}
giArray = giList.toArray(new GalleryInfo[giList.size()]);
}
private void parseLofi(String body) throws Exception {
Pattern p;
Matcher m;
// Get gallery
List<LofiGalleryInfo> lgiList = new ArrayList<>(DEFAULT_LIST_SIZE);
p = Pattern.compile("<td class=\"ii\"><a href=\"(.+?)\">" // detail url
+ "<img src=\"(.+?)\".+?/>" // thumb url
+ ".+?<a class=\"b\" href=\".+?\">(.+?)</a>" // title
+ ".+?<td class=\"ik ip\">Posted:</td><td class=\"ip\">(.+?)</td>" // Posted and uploader
+ "</tr><tr><td class=\"ik\">Category:</td><td>(.+?)</td>" // Category
+ "</tr><tr><td class=\"ik\">Tags:</td><td>(.+?)</td>" // Tags
+ "</tr><tr><td class=\"ik\">Rating:</td><td class=\"ir\">(.+?)</td>"); // rating
m = p.matcher(body);
DetailUrlParser dup = new DetailUrlParser();
String[] pau = new String[2];
while (m.find()) {
LofiGalleryInfo lgi = new LofiGalleryInfo();
dup.parser(m.group(1));
lgi.gid = dup.gid;
lgi.token = dup.token;
lgi.thumb = Utils.unescapeXml(m.group(2));
lgi.title = Utils.unescapeXml(m.group(3));
getPostedAndUploader(m.group(4), pau);
lgi.posted = pau[0];
lgi.uploader = pau[1];
lgi.category = EhUtils.getCategory(m.group(5));
String tags = m.group(6);
if (tags.equals("-"))
lgi.lofiTags = new String[0];
else
lgi.lofiTags = tags.split(", ");
String rating = m.group(7);
if (rating.equals("-"))
lgi.rating = Float.NaN;
else
lgi.rating = getStartNum(rating);
lgi.generateSLang();
lgiList.add(lgi);
}
if (lgiList.size() == 0) {
if (body.contains("No hits found</div>")) {
pageNum = 0;
} else if (body.contains("No more hits found</div>")) {
throw new EhException("Index is out of range");
} else {
throw new EhException(PARSE_MESSAGE);
}
} else {
if (!body.contains("Next Page &gt;</a>")) {
pageNum = CURRENT_PAGE_IS_LAST;
} else {
pageNum = Integer.MAX_VALUE;
}
}
}
public void parse(String body, int source) throws Exception {
AssertUtils.assertNotNull("Body is null when parse gallery list", body);
switch (source) {
default:
case EhClient.SOURCE_G:
case EhClient.SOURCE_EX: {
parse(body);
break;
}
case EhClient.SOURCE_LOFI: {
parseLofi(body);
break;
}
}
}
private String getRate(String rawRate) {
Pattern p = Pattern.compile("\\d+px");
Matcher m = p.matcher(rawRate);
int num1;
int num2;
int rate = 5;
String re;
if (m.find())
num1 = Integer.parseInt(m.group().replace("px", ""));
else
return null;
if (m.find())
num2 = Integer.parseInt(m.group().replace("px", ""));
else
return null;
rate = rate - num1 / 16;
if (num2 == 21) {
rate--;
re = Integer.toString(rate);
re = re + ".5";
} else
re = Integer.toString(rate);
return re;
}
private static final String PAU_SPACER = " by ";
private void getPostedAndUploader(String raw, String[] pau)
throws NotEqualsException {
int index = raw.indexOf(PAU_SPACER);
AssertUtils.assertEquals("Can not parse posted and uploader", index, -1);
pau[0] = raw.substring(0, index);
pau[1] = raw.substring(index + PAU_SPACER.length());
}
private int getStartNum(String str) {
int startNum = 0;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == '*')
startNum++;
}
return startNum;
}
}
@@ -0,0 +1,174 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.ehviewer.data;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.regex.Pattern;
public class GalleryInfo implements Parcelable {
/**
* ISO 639-1
*/
@SuppressWarnings("unused")
public static final String S_LANG_JA = "JA";
public static final String S_LANG_EN = "EN";
public static final String S_LANG_ZH = "ZH";
public static final String S_LANG_NL = "NL";
public static final String S_LANG_FR = "FR";
public static final String S_LANG_DE = "DE";
public static final String S_LANG_HU = "HU";
public static final String S_LANG_IT = "IT";
public static final String S_LANG_KO = "KO";
public static final String S_LANG_PL = "PL";
public static final String S_LANG_PT = "PT";
public static final String S_LANG_RU = "RU";
public static final String S_LANG_ES = "ES";
public static final String S_LANG_TH = "TH";
public static final String S_LANG_VI = "VI";
public static final String[] S_LANGS = {
S_LANG_EN,
S_LANG_ZH,
S_LANG_ES,
S_LANG_KO,
S_LANG_RU,
S_LANG_FR,
S_LANG_PT,
S_LANG_TH,
S_LANG_DE,
S_LANG_IT,
S_LANG_VI,
S_LANG_PL,
S_LANG_HU,
S_LANG_NL,
};
public static final String[] S_LANG_PATTERNS = {
"[(\\[]eng(?:lish)?[)\\]]",
"[((\\[]chinese[))\\]]|[汉漢]化|中[国國][语語]|中文|[(\\[]CN[)\\]]",
"[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]",
"[(\\[]korean?[)\\]]",
"[(\\[]rus(?:sian)?[)\\]]",
"[(\\[]fr(?:ench)?[)\\]]",
"[(\\[]portuguese",
"[(\\[]thai(?: ภาษาไทย)?[)\\]]",
"[(\\[]german[)\\]]",
"[(\\[]italiano?[)\\]]",
"[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]",
"[(\\[]polish[)\\]]",
"[(\\[]hun(?:garian)?[)\\]]",
"[(\\[]dutch[)\\]]",
};
public int gid;
public String token;
public String title;
public String posted;
public int category;
public String thumb;
public String uploader;
public float rating;
/**
* Will not be recond
*/
public int thumbWidth;
/**
* Will not be recond
*/
public int thumbHeight;
/**
* language get from title
*/
public String simpleLanguage;
public static final Creator<GalleryInfo> CREATOR =
new Creator<GalleryInfo>() {
@Override
public GalleryInfo createFromParcel(Parcel source) {
GalleryInfo p = new GalleryInfo();
p.gid = source.readInt();
p.token = source.readString();
p.title = source.readString();
p.posted = source.readString();
p.category = source.readInt();
p.thumb = source.readString();
p.uploader = source.readString();
p.rating = source.readFloat();
p.simpleLanguage = source.readString();
return p;
}
@Override
public GalleryInfo[] newArray(int size) {
return new GalleryInfo[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(gid);
dest.writeString(token);
dest.writeString(title);
dest.writeString(posted);
dest.writeInt(category);
dest.writeString(thumb);
dest.writeString(uploader);
dest.writeFloat(rating);
dest.writeString(simpleLanguage);
}
public GalleryInfo() {
// Empty
}
public GalleryInfo(GalleryInfo gi) {
gid = gi.gid;
token = gi.token;
title = gi.title;
posted = gi.posted;
category = gi.category;
thumb = gi.thumb;
uploader = gi.uploader;
rating = gi.rating;
simpleLanguage = gi.simpleLanguage;
}
@Override
public boolean equals(Object o) {
if (o instanceof GalleryInfo && ((GalleryInfo) o).gid == gid)
return true;
else
return false;
}
public final void generateSLang() {
for (int i = 0; i < S_LANGS.length; i++) {
if (Pattern.compile(S_LANG_PATTERNS[i], Pattern.CASE_INSENSITIVE).matcher(title).find()) {
simpleLanguage = S_LANGS[i];
return;
}
}
simpleLanguage = null;
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.ehviewer.data;
public class ListUrlBuilder {
}
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.ehviewer.data;
import android.os.Parcel;
import org.jetbrains.annotations.NotNull;
public class LofiGalleryInfo extends GalleryInfo {
public @NotNull String[] lofiTags;
public static final Creator<LofiGalleryInfo> CREATOR =
new Creator<LofiGalleryInfo>() {
@Override
public LofiGalleryInfo createFromParcel(Parcel source) {
int length;
LofiGalleryInfo p = new LofiGalleryInfo();
p.gid = source.readInt();
p.token = source.readString();
p.title = source.readString();
p.posted = source.readString();
p.category = source.readInt();
p.thumb = source.readString();
p.uploader = source.readString();
p.rating = source.readFloat();
p.simpleLanguage = source.readString();
length = source.readInt();
p.lofiTags = new String[length];
for (int i = 0; i < length; i++)
p.lofiTags[i] = source.readString();
return p;
}
@Override
public LofiGalleryInfo[] newArray(int size) {
return new LofiGalleryInfo[size];
}
};
public LofiGalleryInfo() {}
public LofiGalleryInfo(GalleryInfo galleryInfo) {
gid = galleryInfo.gid;
token = galleryInfo.token;
title = galleryInfo.title;
posted = galleryInfo.posted;
category = galleryInfo.category;
thumb = galleryInfo.thumb;
uploader = galleryInfo.uploader;
rating = galleryInfo.rating;
simpleLanguage = galleryInfo.simpleLanguage;
lofiTags = new String[0];
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
int length = lofiTags == null ? 0 : lofiTags.length;
dest.writeInt(length);
for (int i = 0; i < length; i++)
dest.writeString(lofiTags[i]);
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* 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
* 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 com.hippo.ehviewer.network;
import com.hippo.network.HttpHelper;
import java.net.HttpURLConnection;
import java.net.URL;
public class EhHttpHelper extends HttpHelper {
@Override
protected String getCookie(URL url) {
return super.getCookie(url);
}
@Override
protected void storeCookie(URL url, String value) {
super.storeCookie(url, value);
}
@Override
protected void onBeforeConnect(HttpURLConnection conn) {
super.onBeforeConnect(conn);
}
}
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.ehviewer.util;
public final class EhUtils {
public static final String EH_DOWNLOAD_FILENAME = ".ehviewer";
public static final int NONE = -1; // Use it for homepage
public static final int MISC = 0x1;
public static final int DOUJINSHI = 0x2;
public static final int MANGA = 0x4;
public static final int ARTIST_CG = 0x8;
public static final int GAME_CG = 0x10;
public static final int IMAGE_SET = 0x20;
public static final int COSPLAY = 0x40;
public static final int ASIAN_PORN = 0x80;
public static final int NON_H = 0x100;
public static final int WESTERN = 0x200;
public static final int UNKNOWN = 0x400;
private static final int[] CATEGORY_VALUES = {
MISC,
DOUJINSHI,
MANGA,
ARTIST_CG,
GAME_CG,
IMAGE_SET,
COSPLAY,
ASIAN_PORN,
NON_H,
WESTERN,
UNKNOWN };
private static final String[][] CATEGORY_STRINGS = {
new String[] { "misc" },
new String[] { "doujinshi" },
new String[] { "manga" },
new String[] { "artistcg", "Artist CG Sets" },
new String[] { "gamecg", "Game CG Sets" },
new String[] { "imageset", "Image Sets" },
new String[] { "cosplay" },
new String[] { "asianporn", "Asian Porn" },
new String[] { "non-h" },
new String[] { "western" },
new String[] { "unknown" }
};
public static int getCategory(String type) {
int i;
for (i = 0; i < CATEGORY_STRINGS.length - 1; i++) {
for (String str : CATEGORY_STRINGS[i])
if (str.equalsIgnoreCase(type))
return CATEGORY_VALUES[i];
}
return CATEGORY_VALUES[i];
}
public static String getCategory(int type) {
int i;
for (i = 0; i < CATEGORY_VALUES.length - 1; i++) {
if (CATEGORY_VALUES[i] == type)
break;
}
return CATEGORY_STRINGS[i][0];
}
}
@@ -0,0 +1,899 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.network;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.MimeTypeMap;
import com.hippo.util.FastByteArrayOutputStream;
import com.hippo.util.Utils;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
/**
* It is not thread safe
*/
public class HttpHelper {
private static final String TAG = HttpHelper.class.getSimpleName();
private static final int MAX_RETRY = 3;
private static final int MAX_REDIRECTS = 3;
private static final int CONNECT_TIMEOUT = 5000;
private static final int READ_TIMEOUT = 5000;
public static final String DEFAULT_USER_AGENT =
"Mozilla/5.0 (Windows NT 6.1; WOW64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/39.0.2171.95 Safari/537.36";
public static final String USER_AGENT =
System.getProperty("http.agent", DEFAULT_USER_AGENT);
private static final int HTTP_TEMP_REDIRECT = 307;
private static final String DEFAULT_CHARSET = "utf-8";
private static final String CHARSET_KEY = "charset=";
private static final CookieManager sCookieManager = CookieManager.getInstance();
private int mResponseCode = -1;
public void reset() {
mResponseCode = -1;
}
public int getResponseCode() {
return mResponseCode;
}
/**
* Get cookie for the url
* @param url the URL
* @return the cookie for the URL
*/
protected String getCookie(URL url) {
return sCookieManager.getCookie(url.toString());
}
/**
* Store cookie for the url
* @param url the URL
* @param value the cookie for the URL
*/
protected void storeCookie(URL url, String value) {
sCookieManager.setCookie(url.toString(), value);
}
/**
* Prepare before connecting
* @param conn the connection
*/
protected void onBeforeConnect(HttpURLConnection conn) {
conn.setInstanceFollowRedirects(true);
conn.setRequestProperty("User-Agent", USER_AGENT);
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
}
private Object doRequst(RequestHelper rh) throws Exception {
URL url;
HttpURLConnection conn = null;
int redirectionCount = 0;
try {
url = rh.getUrl();
while (redirectionCount++ < MAX_REDIRECTS) {
Log.d(TAG, "Request: " + url.toString());
conn = (HttpURLConnection) url.openConnection();
// Prepare before connecting
onBeforeConnect(conn);
// Set cookie
String cookie = getCookie(url);
if (cookie != null) {
conn.setRequestProperty("Cookie", cookie);
}
// Do custom staff
rh.onBeforeConnect(conn);
conn.connect();
// Store cookie
List<String> cookieList = conn.getHeaderFields().get("Set-Cookie");
if (cookieList != null) {
for (String cookieTemp : cookieList) {
if (cookieTemp != null) {
storeCookie(url, cookieTemp);
}
}
}
final int responseCode = conn.getResponseCode();
mResponseCode = responseCode;
Log.d(TAG, "Response code: " + responseCode);
switch (responseCode) {
case HttpURLConnection.HTTP_MOVED_PERM:
case HttpURLConnection.HTTP_MOVED_TEMP:
case HttpURLConnection.HTTP_SEE_OTHER:
case HTTP_TEMP_REDIRECT:
final String location = conn.getHeaderField("Location");
Log.d(TAG, "New location: " + location);
conn.disconnect();
url = new URL(url, location);
break;
default:
return rh.onAfterConnect(conn);
}
}
} finally {
if (conn != null)
conn.disconnect();
}
throw new RedirectionException();
}
private Object requst(RequestHelper rh) throws Exception {
Exception exception = null;
for (int times = 0;
times < MAX_RETRY &&(times == 0 || rh.onRetry(exception));
times++) {
try {
return doRequst(rh);
} catch (Exception e) {
exception = e;
}
}
rh.onRequestFailed(exception);
throw exception;
}
public static interface RequestHelper {
/**
* Get the URL to connect
* @return the URL to connect
*/
public URL getUrl() throws MalformedURLException;
/**
* Add header or do something else for HttpURLConnection before connect
*
* @param conn the connection
* @throws Exception
*/
public void onBeforeConnect(HttpURLConnection conn) throws Exception;
/**
* Get what do you need from HttpURLConnection after connect
* Return null means get error
*
* @param conn the connection
* @return what you want to return
* @throws Exception
*/
public Object onAfterConnect(HttpURLConnection conn) throws Exception;
/**
* Retry http connecting, or stop
*
* @param previousException previous thrown
* @return true for retry, false for stop
*/
public boolean onRetry(Exception previousException);
/**
* Called when request failed by exception
*/
public void onRequestFailed(Exception exception);
}
public static abstract class GetStringHelper implements RequestHelper {
private final String mUrl;
public GetStringHelper(String url) {
mUrl = url;
}
@Override
public URL getUrl() throws MalformedURLException {
return new URL(mUrl);
}
@Override
public void onBeforeConnect(HttpURLConnection conn)
throws Exception {
conn.addRequestProperty("Accept-Encoding", "gzip");
}
private String getCharsetFromContentType(String contentType) {
if (contentType == null) {
return null;
}
String[] values = contentType.split(";");
for (String value : values) {
value = value.trim();
if (value.toLowerCase().startsWith(CHARSET_KEY)) {
return value.substring(CHARSET_KEY.length());
}
}
return null;
}
private String getBody(HttpURLConnection conn)
throws Exception {
String body = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
try {
// First try to get input stream
is = conn.getInputStream();
} catch (Exception t){
// If we get error, get error stream
is = conn.getErrorStream();
}
String encoding = conn.getContentEncoding();
if (encoding != null && encoding.equalsIgnoreCase("gzip"))
is = new GZIPInputStream(is);
int length = conn.getContentLength();
if (length >= 0)
baos = new ByteArrayOutputStream(length);
else
baos = new ByteArrayOutputStream();
Utils.copy(is, baos);
// Get charset
String charset = getCharsetFromContentType(conn.getContentType());
if (charset == null) {
charset = DEFAULT_CHARSET;
}
body = baos.toString(charset);
} finally {
Utils.closeQuietly(is);
Utils.closeQuietly(baos);
}
return body;
}
@Override
public Object onAfterConnect(HttpURLConnection conn)
throws Exception {
return getBody(conn);
}
@Override
public boolean onRetry(Exception previousException) {
// Do not care about exception, just retry
return true;
}
@Override
public void onRequestFailed(Exception exception) {
// Empty
}
}
/**
* RequstHelper for GET method
*/
public static class GetHelper extends GetStringHelper {
public GetHelper(String url) {
super(url);
}
@Override
public void onBeforeConnect(HttpURLConnection conn)
throws Exception {
super.onBeforeConnect(conn);
conn.setRequestMethod("GET");
}
}
/**
* RequstHelper for post form data, use POST method
*/
public static class PostFormHelper extends GetStringHelper {
private final String[][] mArgs;
public PostFormHelper(String url, String[][] args) {
super(url);
mArgs = args;
}
@Override
public void onBeforeConnect(HttpURLConnection conn)
throws Exception {
super.onBeforeConnect(conn);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
StringBuilder sb = new StringBuilder();
int i = 0;
for (String[] arg : mArgs) {
if (i != 0)
sb.append("&");
sb.append(URLEncoder.encode(arg[0], "UTF-8"));
sb.append("=");
sb.append(URLEncoder.encode(arg[1], "UTF-8"));
i++;
}
out.writeBytes(sb.toString());
out.flush();
out.close();
}
}
/**
* RequstHelper for post json, use POST method
*/
public static class PostJsonHelper extends GetStringHelper {
private final JSONObject mJo;
public PostJsonHelper(String url, JSONObject jo) {
super(url);
mJo = jo;
}
@Override
public void onBeforeConnect(HttpURLConnection conn) throws Exception {
super.onBeforeConnect(conn);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
String body = mJo.toString();
Log.d(TAG, "Post json: " + body);
out.writeBytes(body);
out.flush();
out.close();
}
}
public abstract static class FormData {
private final Map<String, String> mProperties;
public FormData() {
mProperties = new LinkedHashMap<String, String>();
}
public void setProperty(String key, String value) {
mProperties.put(key, value);
}
public void clearProperty(String key) {
mProperties.remove(key);
}
public void clearAllProperties() {
mProperties.clear();
}
/**
* Put information to target OutputStream.
*
* @param os target outputStream
* @throws java.io.IOException
*/
public abstract void output(OutputStream os) throws IOException;
public void doOutPut(OutputStream os) throws IOException {
StringBuilder sb = new StringBuilder();
for (String key : mProperties.keySet())
sb.append(key).append(": ").append(mProperties.get(key)).append("\r\n");
sb.append("\r\n");
os.write(sb.toString().getBytes());
output(os);
}
}
public static class StringData extends FormData {
private final String mStr;
public StringData(String str) {
mStr = str;
}
@Override
public void output(OutputStream os) throws IOException {
os.write(mStr.getBytes());
os.write("\r\n".getBytes());
}
}
public static class BitmapData extends FormData {
private final Bitmap mBitmap;
public BitmapData(Bitmap bmp) {
mBitmap = bmp;
setProperty("Content-Type", "image/jpeg");
}
@Override
public void output(OutputStream os) throws IOException {
mBitmap.compress(Bitmap.CompressFormat.JPEG, 98, os);
os.write("\r\n".getBytes());
}
}
public static class FileData extends FormData {
private final File mFile;
public FileData(File file) {
mFile = file;
setProperty("Content-Type",
URLConnection.guessContentTypeFromName(file.getName()));
}
@Override
public void output(OutputStream os) throws IOException {
InputStream is = new FileInputStream(mFile);
byte[] buffer = new byte[1024];
int bytesRead;
while((bytesRead = is.read(buffer)) !=-1)
os.write(buffer, 0, bytesRead);
is.close();
os.write("\r\n".getBytes());
}
}
public static class PostFormDataHelper extends GetStringHelper {
private static final String BOUNDARY = "------WebKitFormBoundary7eDB0hDQ91s22Tkf";
private final List<FormData> mDataList;
public PostFormDataHelper(String url, List<FormData> dataList) {
super(url);
mDataList = dataList;
}
@Override
public void onBeforeConnect(HttpURLConnection conn) throws Exception {
super.onBeforeConnect(conn);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type",
"multipart/form-data; boundary=----WebKitFormBoundary7eDB0hDQ91s22Tkf");
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
for (FormData data : mDataList) {
out.write(BOUNDARY.getBytes());
out.write("\r\n".getBytes());
data.doOutPut(out);
}
out.write(BOUNDARY.getBytes());
out.write("--".getBytes());
out.flush();
out.close();
}
}
public static class GetImageHelper implements RequestHelper {
private final String mUrl;
public GetImageHelper(String url) {
mUrl = url;
}
@Override
public URL getUrl() throws MalformedURLException {
return new URL(mUrl);
}
@Override
public void onBeforeConnect(HttpURLConnection conn)
throws Exception {
conn.setRequestMethod("GET");
}
@Override
public Object onAfterConnect(HttpURLConnection conn)
throws Exception {
// If just
// Bitmap bmp = BitmapFactory.decodeStream(conn.getInputStream(), null, Ui.getBitmapOpt());
// bitmap might be incomplete.
int size = conn.getContentLength();
ByteArrayOutputStream a;
FastByteArrayOutputStream fbaos = new FastByteArrayOutputStream(size == -1 ? 24 * 1024 : (size + 100));
Utils.copy(conn.getInputStream(), fbaos);
Bitmap bmp = BitmapFactory.decodeByteArray(fbaos.getBuffer(), 0, fbaos.size(), null);
if (bmp == null) {
throw new IllegalStateException("Can not decode to bitmap.");
}
return bmp;
}
@Override
public boolean onRetry(Exception previousException) {
// Do not care about exception, just retry
return true;
}
@Override
public void onRequestFailed(Exception exception) {
// Empty
}
}
public static interface OnDownloadListener {
/**
* Called before connecting
*/
public void onStartConnecting();
/**
* Called after connecting and before downloading
*
* @param totalSize content length, -1 for unknown
*/
public void onStartDownloading(int totalSize);
/**
* File name is fixed
*
* @param newName new file name
*/
public void onNameFix(String newName);
/**
* Called repeatedly during downloading
*
* @param downloadSize downloaded size
* @param totalSize content length, -1 for unknown
*/
public void onDownload(int downloadSize, int totalSize);
/**
* Called when download ok
*/
public void onSuccess();
/**
* Called when download stop
*/
public void onStop();
/**
* Called when download failed
*
* @param e the exception why fail
*/
public void onFailure(Exception e);
}
public static class DownloadControlor {
private boolean mStop = false;
public void stop() {
mStop = true;
}
public void reset() {
mStop = false;
}
public boolean isStop() {
return mStop;
}
}
public static class DownloadHelper implements RequestHelper {
private static final String DOWNLOAD_EXTENSION = ".download";
private final String mUrl;
private final File mDir;
private String mFileName;
private File mFile;
private File mTempFile;
private final DownloadControlor mControlor;
private final OnDownloadListener mListener;
private int mContentLength;
private int mReceivedSize;
public DownloadHelper(String url, File dir, String fileName,
DownloadControlor controlor, OnDownloadListener listener) {
mUrl = url;
mDir = dir;
mFileName = fileName;
mControlor = controlor;
mListener = listener;
}
@Override
public URL getUrl() throws MalformedURLException {
return new URL(mUrl);
}
private void checkStop() throws StopRequestException {
if (mControlor != null && mControlor.isStop()) {
throw new StopRequestException();
}
}
@Override
public void onBeforeConnect(HttpURLConnection conn) throws Exception {
checkStop();
conn.setRequestMethod("GET");
// Sometimes for application/octet-stream, we need to add this line
// to get content length
conn.setRequestProperty("Range", "bytes=0-");
if (mListener != null) {
mListener.onStartConnecting();
}
}
private int getContentLength(HttpURLConnection conn) {
int contentLength = conn.getContentLength();
String range;
if (contentLength == -1 &&
(range = conn.getHeaderField("Content-Range")) != null) {
// Content-Range looks like bytes 500-999/1234
contentLength = 0;
int step = 0;
boolean isNum = false;
char ch;
for (int i = 0; i < range.length(); i++) {
ch = range.charAt(i);
if (ch >= '0' && ch <= '9') {
if (!isNum) {
isNum = true;
step++;
}
} else {
isNum = false;
}
if (isNum && step == 3) {
contentLength = contentLength * 10 + ch - '0';
}
}
}
return contentLength;
}
/**
* Get new extension
*
* @param ext new extension
* @return null for not changed
*/
protected String fixNewExtension(String ext) {
return null;
}
@Override
public Object onAfterConnect(HttpURLConnection conn) throws Exception {
// Check stop
if (mControlor != null && mControlor.isStop())
throw new StopRequestException();
String contentType = conn.getContentType();
int index = contentType.indexOf(';');
if (index != -1)
contentType = contentType.substring(0, index);
mContentLength = getContentLength(conn);
if (mListener != null) {
mListener.onStartDownloading(mContentLength);
}
// Fix extension
MimeTypeMap mime = MimeTypeMap.getSingleton();
String newExtension = mime.getExtensionFromMimeType(contentType);
if (newExtension == null) {
index = contentType.lastIndexOf('/');
if (index != -1) {
newExtension = contentType.substring(index + 1);
}
}
newExtension = fixNewExtension(newExtension);
if (newExtension != null) {
index = mFileName.lastIndexOf('.');
if (index == -1) {
mFileName = mFileName + '.' + newExtension;
} else {
mFileName = mFileName.substring(0, index) + '.' + newExtension;
}
}
// Make sure parent exist
if (!mDir.mkdirs() && !mDir.isDirectory()) {
throw new FileNotFoundException("Can not create dir " +
mDir.getPath());
}
mFile = new File(mDir, mFileName);
mTempFile = new File(mDir, mFileName + DOWNLOAD_EXTENSION);
// Transfer
transferData(conn.getInputStream(), new FileOutputStream(mTempFile));
// Get ok, rename
Utils.deleteFile(mFile);
if (!mTempFile.renameTo(mFile)) {
throw new FileNotFoundException("Rename " + mTempFile.getPath() +
" to " + mFile.getPath() + " error");
}
// Callback
if (mListener != null) {
mListener.onSuccess();
}
// We do not need result
return null;
}
private void transferData(InputStream in, OutputStream out)
throws Exception {
final byte data[] = new byte[512 * 1024];
mReceivedSize = 0;
while (true) {
// Check stop first
checkStop();
int bytesRead = in.read(data);
if (bytesRead == -1)
break;
out.write(data, 0, bytesRead);
mReceivedSize += bytesRead;
if (mListener != null) {
mListener.onDownload(mReceivedSize, mContentLength);
}
}
if (mContentLength != -1 && mReceivedSize != mContentLength) {
throw new UncompletedException("Received size is " + mReceivedSize
+ ", but ContentLength is " + mContentLength);
}
}
@Override
public boolean onRetry(Exception previousException) {
// Delete unfinished file
Utils.deleteFile(mTempFile);
Utils.deleteFile(mFile);
if (previousException instanceof StopRequestException) {
return false;
} else {
return true;
}
}
@Override
public void onRequestFailed(Exception exception) {
if (mListener != null) {
if (exception instanceof StopRequestException) {
mListener.onStop();
} else {
mListener.onFailure(exception);
}
}
}
}
/**
* Http GET method
* @param url the url to get
* @return body
*/
public String get(String url) throws Exception {
return (String) requst(new GetHelper(url));
}
/**
* Post form data
* @param url the url to post
* @param args the form to post
* @return body
*/
public String postForm(String url, String[][] args) throws Exception {
return (String) requst(new PostFormHelper(url, args));
}
/**
* Post json data
* @param url the url to post
* @param json the json to post
* @return body
*/
public String postJson(String url, JSONObject json) throws Exception {
return (String) requst(new PostJsonHelper(url, json));
}
/**
* Post data, multipart/form-data
* @param url the url to post
* @param dataList the data list to post
* @return body
*/
public String postFormData(String url, List<FormData> dataList) throws Exception {
return (String)requst(new PostFormDataHelper(url, dataList));
}
/**
* Get image
* @param url the url to get image
* @return bitmap
*/
public Bitmap getImage(String url) throws Exception {
return (Bitmap) requst(new GetImageHelper(url));
}
/**
* Init DownloadHelper
*
* @param url the url to download
* @param dir the dir to download file
* @param file the file name
* @param controlor download control
* @param listener download listener
*/
public void download(String url, File dir, String file,
DownloadControlor controlor, OnDownloadListener listener) throws Exception {
requst(new DownloadHelper(url, dir, file, controlor, listener));
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.network;
public class RedirectionException extends Exception {
public RedirectionException() {
super("To many redirection");
}
}
@@ -0,0 +1,19 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.network;
public class ResponseCodeException extends Exception {
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.network;
public class StopRequestException extends Exception {
public StopRequestException() {
super("Stoped by request");
}
}
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.network;
public class UncompletedException extends Exception {
public UncompletedException() {
super("Uncompleted error");
}
public UncompletedException(String detailMessage) {
super(detailMessage);
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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 com.hippo.network;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class UrlBuilder {
public String mRootUrl;
public Map<String, Object> mQueryMap = new HashMap<String, Object>();
public UrlBuilder(String rootUrl) {
mRootUrl = rootUrl;
}
public void addQuery(String key, Object value) {
mQueryMap.put(key, value);
}
public String build() {
if (mQueryMap.size() == 0) {
return mRootUrl;
} else {
StringBuilder sb = new StringBuilder(mRootUrl);
sb.append("?");
Iterator<String> iter = mQueryMap.keySet().iterator();
if (iter.hasNext()) {
String key = iter.next();
Object value = mQueryMap.get(key);
sb.append(key).append("=").append(value);
}
while (iter.hasNext()) {
String key = iter.next();
Object value = mQueryMap.get(key);
sb.append("&").append(key).append("=").append(value);
}
return sb.toString();
}
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* 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
* 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 com.hippo.util;
public class AssertUtils {
public static void assertNotNull(Object object) throws NullPointerException {
assertNotNull(null, object);
}
public static void assertNotNull(String message, Object object)
throws NullPointerException {
if (object == null) {
throw new NullPointerException(message);
}
}
public static void assertEquals(String message, int expected, int actual)
throws NotEqualsException {
if (expected != actual) {
throw new NotEqualsException(message);
}
}
}
@@ -0,0 +1,230 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.util;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
/**
* It is the same as {@link java.io.ByteArrayOutputStream}, but buffer can be got
* directly. So it is not safe.
*/
public class FastByteArrayOutputStream extends OutputStream {
/**
* The byte array containing the bytes written.
*/
protected byte[] buf;
/**
* The number of bytes written.
*/
protected int count;
/**
* Constructs a new ByteArrayOutputStream with a default size of 32 bytes.
* If more than 32 bytes are written to this instance, the underlying byte
* array will expand.
*/
public FastByteArrayOutputStream() {
buf = new byte[32];
}
/**
* Constructs a new {@code ByteArrayOutputStream} with a default size of
* {@code size} bytes. If more than {@code size} bytes are written to this
* instance, the underlying byte array will expand.
*
* @param size
* initial size for the underlying byte array, must be
* non-negative.
* @throws IllegalArgumentException
* if {@code size} < 0.
*/
public FastByteArrayOutputStream(int size) {
if (size >= 0) {
buf = new byte[size];
} else {
throw new IllegalArgumentException("size < 0");
}
}
/**
* Closes this stream. This releases system resources used for this stream.
*
* @throws java.io.IOException
* if an error occurs while attempting to close this stream.
*/
@Override
public void close() throws IOException {
/**
* Although the spec claims "A closed stream cannot perform output
* operations and cannot be reopened.", this implementation must do
* nothing.
*/
super.close();
}
private void expand(int i) {
/* Can the buffer handle @i more bytes, if not expand it */
if (count + i <= buf.length) {
return;
}
byte[] newbuf = new byte[(count + i) * 2];
System.arraycopy(buf, 0, newbuf, 0, count);
buf = newbuf;
}
/**
* Resets this stream to the beginning of the underlying byte array. All
* subsequent writes will overwrite any bytes previously stored in this
* stream.
*/
public synchronized void reset() {
count = 0;
}
/**
* Returns the total number of bytes written to this stream so far.
*
* @return the number of bytes written to this stream.
*/
public int size() {
return count;
}
/**
* Returns the contents of this ByteArrayOutputStream as a byte array. Any
* changes made to the receiver after returning will not be reflected in the
* byte array returned to the caller.
*
* @return this stream's current contents as a byte array.
*/
public synchronized byte[] getBuffer() {
return buf;
}
/**
* Returns the contents of this ByteArrayOutputStream as a string. Any
* changes made to the receiver after returning will not be reflected in the
* string returned to the caller.
*
* @return this stream's current contents as a string.
*/
@Override
public String toString() {
return new String(buf, 0, count);
}
/**
* Returns the contents of this ByteArrayOutputStream as a string. Each byte
* {@code b} in this stream is converted to a character {@code c} using the
* following function:
* {@code c == (char)(((hibyte & 0xff) << 8) | (b & 0xff))}. This method is
* deprecated and either {@link #toString()} or {@link #toString(String)}
* should be used.
*
* @param hibyte
* the high byte of each resulting Unicode character.
* @return this stream's current contents as a string with the high byte set
* to {@code hibyte}.
* @deprecated Use {@link #toString()} instead.
*/
@Deprecated
public String toString(int hibyte) {
char[] newBuf = new char[size()];
for (int i = 0; i < newBuf.length; i++) {
newBuf[i] = (char) (((hibyte & 0xff) << 8) | (buf[i] & 0xff));
}
return new String(newBuf);
}
/**
* Returns the contents of this ByteArrayOutputStream as a string converted
* according to the encoding declared in {@code charsetName}.
*
* @param charsetName
* a string representing the encoding to use when translating
* this stream to a string.
* @return this stream's current contents as an encoded string.
* @throws java.io.UnsupportedEncodingException
* if the provided encoding is not supported.
*/
public String toString(String charsetName) throws UnsupportedEncodingException {
return new String(buf, 0, count, charsetName);
}
/**
* Writes {@code count} bytes from the byte array {@code buffer} starting at
* offset {@code index} to this stream.
*
* @param buffer
* the buffer to be written.
* @param offset
* the initial position in {@code buffer} to retrieve bytes.
* @param len
* the number of bytes of {@code buffer} to write.
* @throws NullPointerException
* if {@code buffer} is {@code null}.
* @throws IndexOutOfBoundsException
* if {@code offset < 0} or {@code len < 0}, or if
* {@code offset + len} is greater than the length of
* {@code buffer}.
*/
@Override
public synchronized void write(byte[] buffer, int offset, int len) {
if ((offset | count) < 0 || offset > buffer.length || buffer.length - offset < count) {
throw new ArrayIndexOutOfBoundsException("length=" + buffer.length + "; regionStart=" + offset
+ "; regionLength=" + count);
}
if (len == 0) {
return;
}
expand(len);
System.arraycopy(buffer, offset, buf, this.count, len);
this.count += len;
}
/**
* Writes the specified byte {@code oneByte} to the OutputStream. Only the
* low order byte of {@code oneByte} is written.
*
* @param oneByte
* the byte to be written.
*/
@Override
public synchronized void write(int oneByte) {
if (count == buf.length) {
expand(1);
}
buf[count++] = (byte) oneByte;
}
/**
* Takes the contents of this stream and writes it to the output stream
* {@code out}.
*
* @param out
* an OutputStream on which to write the contents of this stream.
* @throws java.io.IOException
* if an error occurs while writing to {@code out}.
*/
public synchronized void writeTo(OutputStream out) throws IOException {
out.write(buf, 0, count);
}
}
@@ -0,0 +1,304 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* 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
* 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 com.hippo.util;
import java.util.Random;
// Get most code from android.util.MathUtils
public final class MathUtils {
private static final Random sRandom = new Random();
private static final float DEG_TO_RAD = 3.1415926f / 180.0f;
private static final float RAD_TO_DEG = 180.0f / 3.1415926f;
public static float abs(float v) {
return v > 0 ? v : -v;
}
public static float log(float a) {
return (float) Math.log(a);
}
public static float exp(float a) {
return (float) Math.exp(a);
}
public static float pow(float a, float b) {
return (float) Math.pow(a, b);
}
public static float max(float a, float b) {
return a > b ? a : b;
}
public static float max(int a, int b) {
return a > b ? a : b;
}
public static float max(float a, float b, float c) {
return a > b ? (a > c ? a : c) : (b > c ? b : c);
}
public static float max(int a, int b, int c) {
return a > b ? (a > c ? a : c) : (b > c ? b : c);
}
public static float max(float... arg) {
int length = arg.length;
if (length <= 0) {
throw new IllegalArgumentException("Empty argument");
} else {
float n = arg[0];
float m;
for (int i = 1; i < length; i++) {
m = arg[i];
if (m > n)
n = m;
}
return n;
}
}
public static int max(int... arg) {
int length = arg.length;
if (length <= 0) {
throw new IllegalArgumentException("Empty argument");
} else {
int n = arg[0];
int m;
for (int i = 1; i < length; i++) {
m = arg[i];
if (m > n)
n = m;
}
return n;
}
}
public static float min(float a, float b) {
return a < b ? a : b;
}
public static float min(int a, int b) {
return a < b ? a : b;
}
public static float min(float a, float b, float c) {
return a < b ? (a < c ? a : c) : (b < c ? b : c);
}
public static float min(int a, int b, int c) {
return a < b ? (a < c ? a : c) : (b < c ? b : c);
}
public static float min(float... arg) {
int length = arg.length;
if (length <= 0) {
throw new IllegalArgumentException("Empty argument");
} else {
float n = arg[0];
float m;
for (int i = 1; i < length; i++) {
m = arg[i];
if (m < n)
n = m;
}
return n;
}
}
public static int min(int... arg) {
int length = arg.length;
if (length <= 0) {
throw new IllegalArgumentException("Empty argument");
} else {
int n = arg[0];
int m;
for (int i = 1; i < length; i++) {
m = arg[i];
if (m < n)
n = m;
}
return n;
}
}
public static float dist(float x1, float y1, float x2, float y2) {
final float x = (x2 - x1);
final float y = (y2 - y1);
return (float) Math.hypot(x, y);
}
public static float dist(float x1, float y1, float z1, float x2, float y2, float z2) {
final float x = (x2 - x1);
final float y = (y2 - y1);
final float z = (z2 - z1);
return (float) Math.sqrt(x * x + y * y + z * z);
}
public static float mag(float a, float b) {
return (float) Math.hypot(a, b);
}
public static float mag(float a, float b, float c) {
return (float) Math.sqrt(a * a + b * b + c * c);
}
public static float sq(float v) {
return v * v;
}
public static float radians(float degrees) {
return degrees * DEG_TO_RAD;
}
public static float degrees(float radians) {
return radians * RAD_TO_DEG;
}
public static float acos(float value) {
return (float) Math.acos(value);
}
public static float asin(float value) {
return (float) Math.asin(value);
}
public static float atan(float value) {
return (float) Math.atan(value);
}
public static float atan2(float a, float b) {
return (float) Math.atan2(a, b);
}
public static float tan(float angle) {
return (float) Math.tan(angle);
}
public static int lerp(int start, int stop, float amount) {
return start + (int) ((stop - start) * amount);
}
public static float lerp(float start, float stop, float amount) {
return start + (stop - start) * amount;
}
public static float norm(float start, float stop, float value) {
return (value - start) / (stop - start);
}
public static float map(float minStart, float minStop, float maxStart, float maxStop, float value) {
return maxStart + (maxStart - maxStop) * ((value - minStart) / (minStop - minStart));
}
/**
* Returns the input value x clamped to the range [min, max].
*
* @param x
* @param min
* @param max
* @return
*/
public static int clamp(int x, int min, int max) {
if (x > max) return max;
if (x < min) return min;
return x;
}
/**
* Returns the input value x clamped to the range [min, max].
*
* @param x
* @param min
* @param max
* @return
*/
public static float clamp(float x, float min, float max) {
if (x > max) return max;
if (x < min) return min;
return x;
}
/**
* Returns the input value x clamped to the range [min, max].
* @param x
* @param min
* @param max
* @return
*/
public static long clamp(long x, long min, long max) {
if (x > max) return max;
if (x < min) return min;
return x;
}
/**
* Returns the next power of two.
* Returns the input if it is already power of 2.
* Throws IllegalArgumentException if the input is <= 0 or
* the answer overflows.
*
* @param n
* @return
*/
public static int nextPowerOf2(int n) {
if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException("n is invalid: " + n);
n -= 1;
n |= n >> 16;
n |= n >> 8;
n |= n >> 4;
n |= n >> 2;
n |= n >> 1;
return n + 1;
}
/**
* Return is a and b is in the same side to standard.
* If one of a and b equals standard, return true.
*
* @param a
* @param b
* @param standard
* @return
*/
public static boolean isSameSide(float a, float b, float standard) {
return (a >= standard && b >= standard) || (a <= standard && b <= standard);
}
public static int random(int howbig) {
return (int) (sRandom.nextFloat() * howbig);
}
public static int random(int howsmall, int howbig) {
if (howsmall >= howbig)
return howsmall;
return (int) (sRandom.nextFloat() * (howbig - howsmall) + howsmall);
}
public static float random(float howbig) {
return sRandom.nextFloat() * howbig;
}
public static float random(float howsmall, float howbig) {
if (howsmall >= howbig)
return howsmall;
return sRandom.nextFloat() * (howbig - howsmall) + howsmall;
}
public static void randomSeed(long seed) {
sRandom.setSeed(seed);
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* 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
* 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 com.hippo.util;
public class NotEqualsException extends Exception {
public NotEqualsException(String detailMessage) {
super(detailMessage);
}
}
+807
View File
@@ -0,0 +1,807 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* 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
* 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 com.hippo.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Vibrator;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;
public final class Utils {
@SuppressWarnings("unused")
private static String TAG = Utils.class.getSimpleName();
public static final DateFormat DATE_FORMATE_FOR_GALLERY = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US);
public static final DateFormat DATE_FORMATE_FOR_UPDATE = new SimpleDateFormat("yyyyMMdd", Locale.US);
public static final String EMPTY_STRING = "";
/**
* Colse is. Don't worry about anything.
*
* @param is
*/
public static void closeQuietly(Closeable is) {
try {
if (is != null)
is.close();
} catch (IOException e) {
}
}
/**
* Put InputStream to File, default bufferSize is 512 * 1024
*
* @param is
* @param file
* @throws java.io.IOException
*/
public static void inputStream2File(InputStream is, File file) throws IOException {
OutputStream os = new FileOutputStream(file);
copy(is, os);
os.close();
}
/**
* Copy from is to os
*
* @param is
* @param os
* @throws java.io.IOException
*/
public static void copy(InputStream is, OutputStream os) throws IOException {
copy(is, os, 512 * 1024);
}
/**
* Copy from is to os
*
* @param is
* @param os
* @param size Buffer size
* @throws java.io.IOException
*/
public static void copy(InputStream is, OutputStream os, int size) throws IOException {
byte[] buffer = new byte[size];
int bytesRead;
while((bytesRead = is.read(buffer)) !=-1)
os.write(buffer, 0, bytesRead);
os.flush();
}
/**
* Get String from InputStream
*
* @param is
* @param charset
* @param size The InputStream size you estimate
* @return
*/
public static String inputStream2String(InputStream is, String charset, int size) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
String str = null;
try {
copy(is, baos, 1024);
str = baos.toString(charset);
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(baos);
}
return str;
}
/**
* Not work for dir, Copy from scr to dst, no matter dst exist or not.
*
* @param src
* @param dst
* @return True for success, false for fail
*/
public static boolean copyFile(File src, File dst) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dst);
copy(fis, fos);
} catch (Throwable e) {
return false;
} finally {
closeQuietly(fis);
closeQuietly(fos);
}
return true;
}
/**
* If you want to copy on new thread, use it to know
* success or not.
*/
public static interface OnCopyFileOverListener {
public void onCopyFileOver(boolean success, File src, File dst);
}
/**
* Copy file in new thread
*
* @param src
* @param dst
* @param l
*/
public static void copyFileInNewThread(final File src, final File dst, final OnCopyFileOverListener l) {
execute(false, new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
return copyFile(src, dst);
}
@Override
protected void onPostExecute(Boolean result) {
if (l != null)
l.onCopyFileOver(result, src, dst);
}
}, (Void[]) null);
}
/**
* Try to delete file or dir and it's children.
*
* @param file The file to deleted.
* @return True if file is delete, children included.
*/
public static boolean deleteFile(File file) {
if (file == null) {
return false;
}
boolean result = true;
File[] files = file.listFiles();
if (files != null) {
for (File f : files)
deleteFile(f);
}
if (file.exists()) {
// Avoid EBUSY (Device or resource busy) error
final File to = new File(file.getAbsolutePath()
+ System.currentTimeMillis());
if (file.renameTo(to))
result &= to.delete();
else
result &= file.delete();
}
return result;
}
/**
* If you want to copy on new thread, use it to know
* success or not.
*/
public static interface OnDeleteFileOverListener {
public void onDeleteFileOver(boolean success, File file);
}
/**
* Start a new thread to delete dir
*/
public static void deleteFileInNewThread(final File file, final OnDeleteFileOverListener l) {
execute(false, new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
return deleteFile(file);
}
@Override
protected void onPostExecute(Boolean result) {
if (l != null)
l.onDeleteFileOver(result, file);
}
}, (Void[]) null);
}
public static int getDateForUpdate() {
return Utils.parseIntSafely(DATE_FORMATE_FOR_UPDATE.format(new Date()), 0);
}
private static String[] SIZE_UNIT = {"%.2f B", "%.2f KB", "%.2f MB", "%.2f GB"};
public static String sizeToString(long size) {
int length = SIZE_UNIT.length;
float sizeFloat = size;
for (int i = 0; i < length - 1; i++) {
if (sizeFloat < 1024)
return String.format(SIZE_UNIT[i], sizeFloat);
sizeFloat /= 1024;
}
return String.format(SIZE_UNIT[length - 1], sizeFloat);
}
/**
* Execute an {@link android.os.AsyncTask} on a thread pool
*
* @param forceSerial True to force the task to run in serial order
* @param task Task to execute
* @param args Optional arguments to pass to
* {@link android.os.AsyncTask#execute(Object[])}
* @param <T> Task argument type
*/
@SafeVarargs
@SuppressLint("NewApi")
public static <T> void execute(final boolean forceSerial, final AsyncTask<T, ?, ?> task,
final T... args) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.DONUT) {
throw new UnsupportedOperationException(
"This class can only be used on API 4 and newer.");
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB || forceSerial) {
task.execute(args);
} else {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, args);
}
}
private static final String[] ESCAPE_CHARATER_LIST = {
"&amp;",
"&lt;",
"&gt;",
"&quot;",
"&#039;",
"&times;"
};
private static final String[] UNESCAPE_CHARATER_LIST = {
"&",
"<",
">",
"\"",
"'",
"×"
};
/**
* Mostly title and url need it
* @param str
* @return
*/
public static String unescapeXml(String str) {
return replaceEach(str, ESCAPE_CHARATER_LIST, UNESCAPE_CHARATER_LIST);
}
/**
* Make file name valid by removing invalid character and set max length
* @param name
* @return
*/
public static String standardizeFilename(String name) {
String result = name;
// Remove invaild char
result = result.replaceAll("[\\\\/:*?\"<>\\|]", "");
// Make sure size
if (result.length() > 255)
result = result.substring(0, 255);
// Avoid start with whitespace or end with whitespace
result = result.replaceAll("^[\\s]+", "").replace("[\\s]+$", "");
return result;
}
/**
* Joins the elements of the provided array into a single String
* containing the provided list of elements.
*
* @param array
* @param separator
* @return
*/
public static String join(final Object[] array,
final Object separator) {
final StringBuilder sb = new StringBuilder(array.length * 16);
for (int i = 0; i < array.length; i++) {
if (i != 0)
sb.append(separator);
sb.append(array[i]);
}
return sb.toString();
}
/**
* <p>
* Replaces all occurrences of Strings within another String.
* </p>
*
* <p>
* A {@code null} reference passed to this method is a no-op, or if
* any "search string" or "string to replace" is null, that replace will be
* ignored. This will not repeat. For repeating replaces, call the
* overloaded method.
* </p>
*
* <pre>
* StringUtils.replaceEach(null, *, *) = null
* StringUtils.replaceEach("", *, *) = ""
* StringUtils.replaceEach("aba", null, null) = "aba"
* StringUtils.replaceEach("aba", new String[0], null) = "aba"
* StringUtils.replaceEach("aba", null, new String[0]) = "aba"
* StringUtils.replaceEach("aba", new String[]{"a"}, null) = "aba"
* StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}) = "b"
* StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}) = "aba"
* StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}) = "wcte"
* (example of how it does not repeat)
* StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}) = "dcte"
* </pre>
*
* @param text
* text to search and replace in, no-op if null
* @param searchList
* the Strings to search for, no-op if null
* @param replacementList
* the Strings to replace them with, no-op if null
* @return the text with any replacements processed, {@code null} if
* null String input
* @throws IllegalArgumentException
* if the lengths of the arrays are not the same (null is ok,
* and/or size 0)
*/
// Get from org.apache.commons.lang3.StringUtils
public static String replaceEach(final String text, final String[] searchList, final String[] replacementList) {
return replaceEach(text, searchList, replacementList, false, 0);
}
/**
* <p>
* Replaces all occurrences of Strings within another String.
* </p>
*
* <p>
* A {@code null} reference passed to this method is a no-op, or if
* any "search string" or "string to replace" is null, that replace will be
* ignored.
* </p>
*
* <pre>
* StringUtils.replaceEach(null, *, *, *) = null
* StringUtils.replaceEach("", *, *, *) = ""
* StringUtils.replaceEach("aba", null, null, *) = "aba"
* StringUtils.replaceEach("aba", new String[0], null, *) = "aba"
* StringUtils.replaceEach("aba", null, new String[0], *) = "aba"
* StringUtils.replaceEach("aba", new String[]{"a"}, null, *) = "aba"
* StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *) = "b"
* StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}, *) = "aba"
* StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}, *) = "wcte"
* (example of how it repeats)
* StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false) = "dcte"
* StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true) = "tcte"
* StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, *) = IllegalStateException
* </pre>
*
* @param text
* text to search and replace in, no-op if null
* @param searchList
* the Strings to search for, no-op if null
* @param replacementList
* the Strings to replace them with, no-op if null
* @param repeat if true, then replace repeatedly
* until there are no more possible replacements or timeToLive < 0
* @param timeToLive
* if less than 0 then there is a circular reference and endless
* loop
* @return the text with any replacements processed, {@code null} if
* null String input
* @throws IllegalStateException
* if the search is repeating and there is an endless loop due
* to outputs of one being inputs to another
* @throws IllegalArgumentException
* if the lengths of the arrays are not the same (null is ok,
* and/or size 0)
*/
// Get from org.apache.commons.lang3.StringUtils
private static String replaceEach(
final String text, final String[] searchList, final String[] replacementList, final boolean repeat, final int timeToLive) {
// mchyzer Performance note: This creates very few new objects (one major goal)
// let me know if there are performance requests, we can create a harness to measure
if (text == null || TextUtils.isEmpty(text) || searchList == null ||
searchList.length == 0 || replacementList == null || replacementList.length == 0) {
return text;
}
// if recursing, this shouldn't be less than 0
if (timeToLive < 0) {
throw new IllegalStateException("Aborting to protect against StackOverflowError - " +
"output of one loop is the input of another");
}
final int searchLength = searchList.length;
final int replacementLength = replacementList.length;
// make sure lengths are ok, these need to be equal
if (searchLength != replacementLength) {
throw new IllegalArgumentException("Search and Replace array lengths don't match: "
+ searchLength
+ " vs "
+ replacementLength);
}
// keep track of which still have matches
final boolean[] noMoreMatchesForReplIndex = new boolean[searchLength];
// index on index that the match was found
int textIndex = -1;
int replaceIndex = -1;
int tempIndex = -1;
// index of replace array that will replace the search string found
// NOTE: logic duplicated below START
for (int i = 0; i < searchLength; i++) {
if (noMoreMatchesForReplIndex[i] || searchList[i] == null ||
TextUtils.isEmpty(searchList[i]) || replacementList[i] == null) {
continue;
}
tempIndex = text.indexOf(searchList[i]);
// see if we need to keep searching for this
if (tempIndex == -1) {
noMoreMatchesForReplIndex[i] = true;
} else {
if (textIndex == -1 || tempIndex < textIndex) {
textIndex = tempIndex;
replaceIndex = i;
}
}
}
// NOTE: logic mostly below END
// no search strings found, we are done
if (textIndex == -1) {
return text;
}
int start = 0;
// get a good guess on the size of the result buffer so it doesn't have to double if it goes over a bit
int increase = 0;
// count the replacement text elements that are larger than their corresponding text being replaced
for (int i = 0; i < searchList.length; i++) {
if (searchList[i] == null || replacementList[i] == null) {
continue;
}
final int greater = replacementList[i].length() - searchList[i].length();
if (greater > 0) {
increase += 3 * greater; // assume 3 matches
}
}
// have upper-bound at 20% increase, then let Java take over
increase = Math.min(increase, text.length() / 5);
final StringBuilder buf = new StringBuilder(text.length() + increase);
while (textIndex != -1) {
for (int i = start; i < textIndex; i++) {
buf.append(text.charAt(i));
}
buf.append(replacementList[replaceIndex]);
start = textIndex + searchList[replaceIndex].length();
textIndex = -1;
replaceIndex = -1;
tempIndex = -1;
// find the next earliest match
// NOTE: logic mostly duplicated above START
for (int i = 0; i < searchLength; i++) {
if (noMoreMatchesForReplIndex[i] || searchList[i] == null ||
TextUtils.isEmpty(searchList[i]) || replacementList[i] == null) {
continue;
}
tempIndex = text.indexOf(searchList[i], start);
// see if we need to keep searching for this
if (tempIndex == -1) {
noMoreMatchesForReplIndex[i] = true;
} else {
if (textIndex == -1 || tempIndex < textIndex) {
textIndex = tempIndex;
replaceIndex = i;
}
}
}
// NOTE: logic duplicated above END
}
final int textLength = text.length();
for (int i = start; i < textLength; i++) {
buf.append(text.charAt(i));
}
final String result = buf.toString();
if (!repeat) {
return result;
}
return replaceEach(result, searchList, replacementList, repeat, timeToLive - 1);
}
/**
* 0 for false, Non 0 for true
*
* @param integer
* @return
*/
public static boolean int2boolean(int integer) {
return integer != 0;
}
/**
* false for 0, true for 1
*
* @param bool
* @return
*/
public static int boolean2int(boolean bool) {
return bool ? 1 : 0;
}
/**
* Returns the ASCII characters up to but not including the next "\r\n", or
* "\n".
*
* @throws java.io.EOFException if the stream is exhausted before the next
* newline character.
*/
public static String readAsciiLine(final InputStream in) throws IOException {
final StringBuilder result = new StringBuilder(80);
while (true) {
final int c = in.read();
if (c == -1) {
throw new EOFException();
} else if (c == '\n') {
break;
}
result.append((char)c);
}
final int length = result.length();
if (length > 0 && result.charAt(length - 1) == '\r') {
result.setLength(length - 1);
}
return result.toString();
}
/**
* Try to make dir exists
*
* @param dir
* @param force
*/
public static void ensureDir(File dir, boolean force) {
if (!dir.exists()) {
dir.mkdirs();
} else if (force && dir.isFile()) {
dir.delete();
dir.mkdirs();
}
}
/**
* Return true if point is in the rect
*
* @param area
* @param x
* @param y
* @return
*/
public static boolean isInArea(int[] area, int x, int y) {
if (area == null)
throw new NullPointerException("area == null");
if (area.length != 4)
throw new IllegalArgumentException(
"area's length should be 4, but it's length is " + area.length);
if (x >= area[0] && x < area[2] && y >= area[1] && y < area[3])
return true;
else
return false;
}
/**
* Is color is opaque
*
* @param color
* @return
*/
// TODO put it to UiUtils.class
public static boolean isOpaque(int color) {
return color >>> 24 == 0xFF;
}
/**
* Throws AssertionError if the input is false.
* @param cond
*/
public static void assertTrue(boolean cond) {
if (!cond) {
throw new AssertionError();
}
}
/**
* vibrator
*
* @param context
* @param milliseconds Duration
*/
public static void vibrator(Context context, long milliseconds) {
// If user force disable vibrator permission, may throw error
try {
Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
v.vibrate(milliseconds);
} catch (Throwable e) {
}
}
// Removes duplicate adjacent slashes and any trailing slash.
private static String fixSlashes(String origPath) {
// Remove duplicate adjacent slashes.
boolean lastWasSlash = false;
char[] newPath = origPath.toCharArray();
int length = newPath.length;
int newLength = 0;
for (int i = 0; i < length; ++i) {
char ch = newPath[i];
if (ch == '/') {
if (!lastWasSlash) {
newPath[newLength++] = File.separatorChar;
lastWasSlash = true;
}
} else {
newPath[newLength++] = ch;
lastWasSlash = false;
}
}
// Remove any trailing slash (unless this is the root of the file system).
if (lastWasSlash && newLength > 1) {
newLength--;
}
// Reuse the original string if possible.
return (newLength != length) ? new String(newPath, 0, newLength) : origPath;
}
// Joins two path components, adding a separator only if necessary.
public static String joinPath(String dirPath, String name) {
if (name == null) {
throw new NullPointerException("name == null");
}
if (dirPath == null || TextUtils.isEmpty(dirPath)) {
return fixSlashes(name);
} else if (TextUtils.isEmpty(name)) {
return fixSlashes(dirPath);
} else {
int prefixLength = dirPath.length();
boolean haveSlash = (prefixLength > 0 && dirPath.charAt(prefixLength - 1) == File.separatorChar);
if (!haveSlash) {
haveSlash = (name.length() > 0 && name.charAt(0) == File.separatorChar);
}
return fixSlashes(haveSlash ? (dirPath + name) : (dirPath + File.separatorChar + name));
}
}
public static int parseIntSafely(String str, int defaultValue) {
try {
return Integer.parseInt(str);
} catch (Throwable e) {
return defaultValue;
}
}
public static float parseFloatSafely(String str, float defaultValue) {
try {
return Float.parseFloat(str);
} catch (Throwable e) {
return defaultValue;
}
}
public static int[] toIntArray(Collection<? extends Number> collection) {
Object[] boxedArray = collection.toArray();
int len = boxedArray.length;
int[] array = new int[len];
for (int i = 0; i < len; i++) {
// checkNotNull for GWT (do not optimize)
array[i] = ((Number) (boxedArray[i])).intValue();
}
return array;
}
// Throws NullPointerException if the input is null.
public static <T> T checkNotNull(T object) {
if (object == null) throw new NullPointerException();
return object;
}
/**
* Get extension from target string, int lower case
*
* @param name
* @param defautl
* @return
*/
public static String getExtension(String name, String defautl) {
String ext = MimeTypeMap.getFileExtensionFromUrl(name);
if (TextUtils.isEmpty(ext))
ext = defautl;
return ext;
}
/**
* It will return null if can't find mime
*
* @param url
* @return
*/
public static String getMimeType(String url) {
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension != null) {
MimeTypeMap mime = MimeTypeMap.getSingleton();
type = mime.getMimeTypeFromExtension(extension);
}
return type;
}
/**
* If get error return "";
*
* @param key
* @return
*/
public static String getSystemProperties(String key) {
try {
@SuppressWarnings("rawtypes")
Class c = Class.forName("android.os.SystemProperties");
@SuppressWarnings("unchecked")
Method m = c.getDeclaredMethod("get", String.class);
m.setAccessible(true);
String result = (String) m.invoke(null, key);
return result == null ? EMPTY_STRING : result;
} catch (Throwable e) {
return EMPTY_STRING;
}
}
}
@@ -198,7 +198,8 @@ public final class ViewUtils {
* Set the enabled state of this view.
*
* @param v the view to set the enabled state
* @param visibility One of GONE, INVISIBLE, or GONE.
* @param visibility One of {@link android.view.View#GONE},
* {@link android.view.View#INVISIBLE}, or {@link android.view.View#GONE}.
*/
public static void setVisibility(View v, int visibility) {
if (visibility != v.getVisibility()) {