/* * Copyright (C) 2014 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.ehviewer.util; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.EOFException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Build; import android.os.Vibrator; import android.view.View; public final class Utils { @SuppressWarnings("unused") private static String TAG = "Util"; /** * Put InputStream to File, default bufferSize is 512 * 1024 * * @param is * @param file * @throws IOException */ public static void inputStream2File(InputStream is, File file) throws IOException { OutputStream os = new FileOutputStream(file); copy(is, os); } public static void copy(InputStream is, OutputStream os) throws IOException { copy(is, os, 512 * 1024); } 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); is.close(); os.flush(); os.close(); buffer = null; } public static boolean isNumber(String str) { for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); if (ch < '0' || ch > '9') return false; } return true; } public static String byteArrayToHexString(byte[] b) { StringBuilder sb = new StringBuilder(b.length * 2); for (byte element : b) { int v = element & 0xFF; if (v < 16) { sb.append('0'); } sb.append(Integer.toHexString(v)); } return sb.toString().toUpperCase(Locale.getDefault()); } public static byte[] hexStringToByteArray(String s) { int len = s.length()/2*2; byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[(i / 2)] = ((byte) ((Character.digit(s.charAt(i), 16) << 4) + Character .digit(s.charAt(i + 1), 16))); } return data; } /** * Convernt context in stream to string * * @param is * @param charset * @return */ public static String InputStream2String(InputStream is, String charset) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); String str = null; try { Utils.copy(is, baos, 1024); str = baos.toString(charset); } catch (IOException e) { e.printStackTrace(); } finally { Utils.closeQuietly(baos); } return str; } public static String[] getStrings(SharedPreferences shaper, String key) { String str = shaper.getString(key, null); if (str == null || str.length() == 0) return null; return new String(Utils.hexStringToByteArray(str)).split("\n"); } public static void putStrings(SharedPreferences shaper, String key, List strs) { StringBuffer sb = new StringBuffer(); for (String item : strs) { sb.append(item); sb.append('\n'); } int length = sb.length(); if (length != 0) sb.delete(length - 1, length); shaper.edit().putString(key, byteArrayToHexString(sb.toString().getBytes())).apply(); } public static void closeQuietly (Closeable is) { try { if (is != null) is.close(); } catch (IOException e) { } } /** * Try to delete file, dir and it's children * * @param dir * The dir to deleted */ public static void deleteFile(File file) { File[] files = file.listFiles(); if (files != null) { for (File f : files) deleteFile(f); } file.delete(); } /** * Start a new thread to delete dir */ public static void deleteDirInThread(final File file) { new BgThread() { @Override public void run() { deleteFile(file); } }.start(); } // TODO @SuppressLint("SimpleDateFormat") public static int getDate() { int time = 0; try { time = Integer.parseInt(new SimpleDateFormat("yyyyMMdd").format(new Date())); } catch (NumberFormatException e) {} return time; } 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; i++) { if (sizeFloat < 1024 || i == length - 1) { return String.format(SIZE_UNIT[i], sizeFloat); } sizeFloat /= 1024; } return null; } /** * Execute an {@link 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 AsyncTask#execute(Object[])} * @param Task argument type */ @SuppressLint("NewApi") public static void execute(final boolean forceSerial, final AsyncTask 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); } } /** * Method that removes the support for HardwareAcceleration from a {@link View}.
*
* Check AOSP notice:
*
     * 'ComposeShader can only contain shaders of different types (a BitmapShader and a
     * LinearGradient for instance, but not two instances of BitmapShader)'. But, 'If your
     * application is affected by any of these missing features or limitations, you can turn
     * off hardware acceleration for just the affected portion of your application by calling
     * setLayerType(View.LAYER_TYPE_SOFTWARE, null).'
* * @param v The view */ public static void removeHardwareAccelerationSupport(View v) { if (v.getLayerType() != View.LAYER_TYPE_SOFTWARE) { v.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } } private static final String[] ESCAPE_CHARATER_LIST = { "&", "<", ">", """, "'", "×" }; 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) { name = name.replaceAll("[\\\\/:*?\"<>\\|]", ""); name = name.length() > 255 ? name.substring(0, 255) : name; return name.replaceAll("^[\\s]+", "").replace("[\\s]+$", ""); } /** * 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(); } /** *

* Replaces all occurrences of Strings within another String. *

* *

* 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. *

* *
     *  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"
     * 
* * @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); } /** *

* Replaces all occurrences of Strings within another String. *

* *

* 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. *

* *
     *  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
     * 
* * @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 || text.isEmpty() || 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 || searchList[i].isEmpty() || 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 || searchList[i].isEmpty() || 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 : true; } /** * 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(); } } /** * Get extension from target string, int lower case * * @param name * @param defautl * @return */ public static String getExtension(String name, String defautl) { int index = name.lastIndexOf('.'); if (index == -1 || index == name.length() - 1) return defautl; else return name.substring(index + 1).toLowerCase(); } /** * 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.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; } 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 */ public static void vibrator(Context context, long milliseconds) { Vibrator v = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE); v.vibrate(milliseconds); } }