package jane.core; import java.io.ByteArrayOutputStream; 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.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.NodeList; import jane.core.map.ConcurrentLRUMap; import jane.core.map.LongConcurrentHashMap; import jane.core.map.LongConcurrentLRUMap; import jane.core.map.LongMap; /** * 工具类(静态类) */ public final class Util { private static final Random _rand = new Random(); private static final Pattern _patCharset = Pattern.compile("charset=(\\S+)"); private static final int HTTP_REQ_CONNECTION_TIMEOUT = 15 * 1000; private static final int HTTP_RES_BUFFER_SIZE = 8 * 1024; public static Random getRand() { return _rand; } /** * 创建普通的ConcurrentHashMap * <p> * 初始hash空间是16,负载因子是0.5,并发级别等于{@link Const#dbThreadCount}<br> */ public static <K, V> ConcurrentMap<K, V> newConcurrentHashMap() { return new ConcurrentHashMap<>(16, 0.5f, Const.dbThreadCount); } /** * 使用{@link ConcurrentLRUMap}创建可并发带LRU自动丢弃的HashMap */ public static <K, V> Map<K, V> newConcurrentLRUMap(int maxCount, String name) { if(maxCount <= 0) return newConcurrentHashMap(); return new ConcurrentLRUMap<>(maxCount, 0.5f, Const.dbThreadCount, name); // return new ConcurrentLinkedHashMap.Builder().concurrencyLevel(Const.dbThreadCount) // .maximumWeightedCapacity(maxCount).initialCapacity(maxCount).<K, V>build(); } /** * 使用{@link LongConcurrentLRUMap}创建可并发带LRU自动丢弃的HashMap */ public static <V> LongMap<V> newLongConcurrentLRUMap(int maxCount, String name) { if(maxCount <= 0) return new LongConcurrentHashMap<>(16, 0.5f, Const.dbThreadCount); return new LongConcurrentLRUMap<>(maxCount, 0.5f, Const.dbThreadCount, name); // return new ConcurrentLinkedHashMap.Builder().concurrencyLevel(Const.dbThreadCount) // .maximumWeightedCapacity(maxCount).initialCapacity(maxCount).<V>buildLong(); } /** * 逐字节比较两个字节数组 */ public static int compareBytes(byte[] data1, byte[] data2) { int n1 = data1.length, n2 = data2.length; int n = (n1 < n2 ? n1 : n2); for(int i = 0; i < n; ++i) { int c = (data1[i] & 0xff) - (data2[i] & 0xff); if(c != 0) return c; } return n1 - n2; } /** * 比较两个序列容器里的元素是否完全相同(包括顺序相同) */ public static <T extends Comparable<T>> int compareTo(Collection<T> a, Collection<T> b) { int c = a.size() - b.size(); if(c != 0) return c; Iterator<T> ia = a.iterator(); Iterator<T> ib = b.iterator(); while(ia.hasNext()) { c = ia.next().compareTo(ib.next()); if(c != 0) return c; } return 0; } /** * 比较两个Map容器里的元素是否完全相同(包括顺序相同) */ public static <K extends Comparable<K>, V extends Comparable<V>> int compareTo(Map<K, V> a, Map<K, V> b) { int c = a.size() - b.size(); if(c != 0) return c; Iterator<Entry<K, V>> ia = a.entrySet().iterator(); Iterator<Entry<K, V>> ib = b.entrySet().iterator(); while(ia.hasNext()) { Entry<K, V> ea = ia.next(); Entry<K, V> eb = ib.next(); c = ea.getKey().compareTo(eb.getKey()); if(c != 0) return c; c = ea.getValue().compareTo(eb.getValue()); if(c != 0) return c; } return 0; } /** * 把src容器的内容深度拷贝覆盖到dst容器中 * @return dst容器 */ @SuppressWarnings("unchecked") public static <V> Collection<V> appendDeep(Collection<V> src, Collection<V> dst) { if(src != null && !src.isEmpty() && src != dst) { V v = src.iterator().next(); if(v instanceof Bean) { for(V vv : src) dst.add((V)((Bean<?>)vv).clone()); } else dst.addAll(src); } return dst; } /** * 把src容器的内容深度拷贝覆盖到dst容器中 * @return dst容器 */ @SuppressWarnings("unchecked") public static <K, V> Map<K, V> appendDeep(Map<K, V> src, Map<K, V> dst) { if(src != null && !src.isEmpty() && src != dst) { V v = src.values().iterator().next(); if(v instanceof Bean) { for(Entry<K, V> e : src.entrySet()) dst.put(e.getKey(), (V)((Bean<?>)e.getValue()).clone()); } else dst.putAll(src); } return dst; } /** * 把序列容器里的元素转成字符串输出到{@link StringBuilder}中 */ public static StringBuilder append(StringBuilder s, Collection<?> c) { if(c.isEmpty()) return s.append("{},"); s.append('{'); for(Object o : c) s.append(o).append(','); s.setCharAt(s.length() - 1, '}'); return s.append(','); } /** * 把Map容器里的元素转成字符串输出到{@link StringBuilder}中 */ public static StringBuilder append(StringBuilder s, Map<?, ?> m) { if(m.isEmpty()) return s.append("{},"); s.append('{'); for(Entry<?, ?> e : m.entrySet()) s.append(e.getKey()).append(',').append(e.getValue()).append(';'); s.setCharAt(s.length() - 1, '}'); return s.append(','); } /** * 把字符串转化成Lua字符串输出到{@link StringBuilder}中 */ public static StringBuilder toLuaStr(StringBuilder sb, String str) { int n = str.length(); if(sb == null) sb = new StringBuilder(n + 4); sb.append('"'); for(int i = 0; i < n; ++i) { char c = str.charAt(i); switch(c) { case '\\': case '"': sb.append('\\').append(c); break; default: if(c < ' ') // 0x20 sb.append('\\').append('x').append(Octets.toHexNumber(c >> 4)).append(Octets.toHexNumber(c)); else sb.append(c); } } return sb.append('"'); } /** * 把字符串转化成Java/JSON字符串输出到{@link StringBuilder}中 */ public static StringBuilder toJStr(StringBuilder sb, String str) { int n = str.length(); if(sb == null) sb = new StringBuilder(n + 4); sb.append('"'); for(int i = 0; i < n; ++i) { char c = str.charAt(i); switch(c) { case '\\': case '"': sb.append('\\').append(c); break; case '\b': sb.append('\\').append('b'); break; case '\t': sb.append('\\').append('t'); break; case '\n': sb.append('\\').append('n'); break; case '\f': sb.append('\\').append('f'); break; case '\r': sb.append('\\').append('r'); break; default: if(c < ' ') // 0x20 sb.append(c < 0x10 ? "\\u000" : "\\u001").append(Octets.toHexNumber(c)); else sb.append(c); } } return sb.append('"'); } /** * 把普通对象转成JSON字符串输出到{@link StringBuilder}中 */ public static StringBuilder appendJson(StringBuilder s, Object o) { if(o instanceof Number) return s.append(o.toString()); else if(o instanceof Octets) return ((Octets)o).dumpJStr(s); else if(o instanceof Bean) return ((Bean<?>)o).toJson(s); else return toJStr(s, o.toString()); } /** * 把序列容器里的元素转成JSON字符串输出到{@link StringBuilder}中 */ public static StringBuilder appendJson(StringBuilder s, Collection<?> c) { if(c.isEmpty()) return s.append("[],"); s.append('['); for(Object o : c) appendJson(s, o).append(','); s.setCharAt(s.length() - 1, ']'); return s.append(','); } /** * 把Map容器里的元素转成JSON字符串输出到{@link StringBuilder}中 */ public static StringBuilder appendJson(StringBuilder s, Map<?, ?> m) { if(m.isEmpty()) return s.append("{},"); s.append('{'); for(Entry<?, ?> e : m.entrySet()) { appendJson(s, e.getKey()).append(':'); appendJson(s, e.getValue()).append(','); } s.setCharAt(s.length() - 1, '}'); return s.append(','); } /** * 把普通对象转成Lua字符串输出到{@link StringBuilder}中 */ public static StringBuilder appendLua(StringBuilder s, Object o) { if(o instanceof Number) return s.append(o.toString()); else if(o instanceof Octets) return ((Octets)o).dumpJStr(s); else if(o instanceof Bean) return ((Bean<?>)o).toLua(s); else return toJStr(s, o.toString()); } /** * 把序列容器里的元素转成Lua字符串输出到{@link StringBuilder}中 */ public static StringBuilder appendLua(StringBuilder s, Collection<?> c) { if(c.isEmpty()) return s.append("{},"); s.append('{'); for(Object o : c) appendLua(s, o).append(','); s.setCharAt(s.length() - 1, '}'); return s.append(','); } /** * 把Map容器里的元素转成Lua字符串输出到{@link StringBuilder}中 */ public static StringBuilder appendLua(StringBuilder s, Map<?, ?> m) { if(m.isEmpty()) return s.append("{},"); s.append('{'); for(Entry<?, ?> e : m.entrySet()) { s.append('['); appendLua(s, e.getKey()).append(']').append('='); appendLua(s, e.getValue()).append(','); } s.setCharAt(s.length() - 1, '}'); return s.append(','); } /** * 持续从输入流中读取指定长度的数据 */ public static void readStream(InputStream is, String isName, byte[] buf, int len) throws IOException { for(int n = 0; n < len;) { int r = is.read(buf, n, len - n); if(r < 0) throw new IOException(String.format("read interrupt(%s:%d)", isName, n)); n += r; } } public static byte[] readStreamData(InputStream is) throws IOException { if(is == null) return null; int len = is.available(); byte[] data = new byte[len]; readStream(is, "", data, len); return data; } /** * 读取整个文件内容 * @param fileName 文件名 * @return 返回完整的文件内容 */ public static byte[] readFileData(String fileName) throws IOException { try(InputStream is = new FileInputStream(fileName)) { return readStreamData(is); } } /** * 复制文件 * @param srcfile 源文件 * @param dstFile 目标文件 * @return 返回复制的字节数量 */ public static long copyFile(FileChannel srcFc, File dstFile) throws IOException { long r = 0; try(FileOutputStream fos = new FileOutputStream(dstFile)) { ByteBuffer bb = ByteBuffer.allocate(32768); for(int n; (n = srcFc.read(bb)) != -1;) { bb.flip(); fos.write(bb.array(), 0, bb.limit()); bb.clear(); r += n; } } return r; } public static long copyFile(File srcFile, File dstFile) throws IOException { try(FileInputStream fis = new FileInputStream(srcFile)) { return copyFile(fis.getChannel(), dstFile); } } public static final class SundaySearch { private final byte[] _pat; private final int _patLen; private final int[] _skip = new int[256]; public SundaySearch(byte[] pat, int patLen) { _patLen = (patLen < 0 ? 0 : (patLen > pat.length ? pat.length : patLen)); _pat = Arrays.copyOf(pat, _patLen); Arrays.fill(_skip, _patLen + 1); for(int i = 0; i < _patLen; ++i) _skip[_pat[i] & 0xff] = _patLen - i; } public SundaySearch(Octets pat) { this(pat.array(), pat.size()); } public int find(byte[] src, int srcPos, int srcLen) { if(_patLen <= 0) return 0; if(srcPos < 0) srcPos = 0; if(srcPos + srcLen > src.length) srcLen = src.length - srcPos; if(srcLen <= 0) return -1; byte c = _pat[0]; for(int srcEnd = srcLen - _patLen; srcPos <= srcEnd; srcPos += _skip[src[srcPos + _patLen] & 0xff]) { if(src[srcPos] == c) { for(int k = 1;; ++k) { if(k >= _patLen) return srcPos; if(src[srcPos + k] != _pat[k]) break; } } if(srcPos == srcEnd) return -1; } return -1; } public int find(Octets src, int srcPos) { return find(src.array(), srcPos, src.size()); } } public static InputStream createStreamInZip(ZipFile zipFile, String path) throws IOException { ZipEntry ze = zipFile.getEntry(path); return ze != null ? zipFile.getInputStream(ze) : null; } public static byte[] readDataInZip(ZipFile zipFile, String path) throws IOException { try(InputStream is = createStreamInZip(zipFile, path)) { return readStreamData(is); } } public static byte[] readDataInZip(String zipPath, String path) throws IOException { try(ZipFile zf = new ZipFile(zipPath)) { return readDataInZip(zf, path); } } public static InputStream createStreamInJar(Class<?> cls, String path) throws IOException { Enumeration<URL> urls = cls.getClassLoader().getResources(path); return urls.hasMoreElements() ? urls.nextElement().openStream() : null; } public static byte[] readDataInJar(Class<?> cls, String path) throws IOException { try(InputStream is = createStreamInJar(cls, path)) { return readStreamData(is); } } /** * 把xml文件转换成bean的map结构 * <p> * 此调用在发现错误时会抛出异常,注意检查 * @param xmlFile 输入的xml文件名. 文件格式必须是{@link jane.tool.XlsxExport}输出的xml结构 * @param beanMap 输出的map结构. 如果输入的xml文件没有指定key,则必须用Integer类型的key * @param keyCls map的key类. 如果为null,则自动生成从1开始的Integer作为key,此时beanmap的key类型必须是Integer * @param beanCls map的value类. 必须是继承bean类型的 * @param enumMap 常量枚举表. 可以为null */ @SuppressWarnings("unchecked") public static <K, B> void xml2BeanMap(String xmlFile, Map<K, B> beanMap, Class<K> keyCls, Class<B> beanCls, Map<String, ?> enumMap) throws Exception { beanMap.clear(); try(InputStream is = new FileInputStream(xmlFile)) { Element elem = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is).getDocumentElement(); String keyStr = elem.getAttribute("key").trim(); Constructor<K> conKey = (keyCls != null ? keyCls.getConstructor(String.class) : null); Field[] fields = beanCls.getDeclaredFields(); int nField = fields.length; Constructor<?>[] conField = new Constructor<?>[nField]; HashMap<Class<?>, Class<?>> clsMap = new HashMap<>(); clsMap.put(byte.class, Byte.class); clsMap.put(short.class, Short.class); clsMap.put(int.class, Integer.class); clsMap.put(long.class, Long.class); clsMap.put(float.class, Float.class); clsMap.put(double.class, Double.class); for(int i = 0; i < nField; ++i) { Field field = fields[i]; if((field.getModifiers() & (Modifier.STATIC | Modifier.FINAL)) == 0) { fields[i].setAccessible(true); Class<?> cls = clsMap.get(fields[i].getType()); if(cls != null) conField[i] = cls.getConstructor(String.class); } } NodeList nl = elem.getElementsByTagName("record"); for(int i = 0, n = nl.getLength(); i < n; ++i) { elem = (Element)nl.item(i); String str = (keyStr.isEmpty() ? null : elem.getAttribute(keyStr).trim()); K key; try { key = (conKey != null && str != null ? conKey.newInstance(str) : (K)Integer.valueOf(i + 1)); } catch(Exception e) { throw new IllegalStateException("invalid key in record=" + i + ", str=\"" + str + "\" in " + xmlFile, e); } B bean = beanCls.newInstance(); for(int j = 0; j < nField; ++j) { Field field = fields[j]; String fieldName = field.getName(); String fieldValue = elem.getAttribute(fieldName).trim(); if(!fieldValue.isEmpty()) { elem.removeAttribute(fieldName); try { if(enumMap != null && ((fieldValue.charAt(0) - 'A') & 0xff) < 26) // A-Z开头的先查枚举常量表 { Object v = enumMap.get(fieldValue); if(v != null) fieldValue = v.toString(); } if(field.getType() == boolean.class) field.setBoolean(bean, fieldValue.equals("1") || fieldValue.equalsIgnoreCase("true")); else if(field.getType() == String.class) field.set(bean, fieldValue.intern()); // 字段是字符串类型的话,优化到字符串池里 else { Constructor<?> con = conField[j]; if(con == null) throw new IllegalStateException("unsupported field type"); field.set(bean, con.newInstance(fieldValue)); } } catch(Exception e) { throw new IllegalStateException("invalid data in key:" + keyStr + "=\"" + key + "\", field=\"" + fieldName + "\", str=\"" + fieldValue + "\", type=\"" + field.getType().getName() + "\" in " + xmlFile, e); } } } if(!keyStr.isEmpty()) elem.removeAttribute(keyStr); NamedNodeMap nnm = elem.getAttributes(); int nAttr = nnm.getLength(); if(nAttr > 0) { StringBuilder sb = new StringBuilder("unknown field name(s) \""); for(int j = 0;;) { sb.append(nnm.item(j).getNodeName()); if(++j < nAttr) sb.append(','); else throw new IllegalStateException(sb.append("\" in key=\"").append(key).append("\" in ").append(xmlFile).toString()); } } if(beanMap.put(key, bean) != null) throw new IllegalStateException("duplicate key:" + keyStr + "=\"" + key + "\" in " + xmlFile); } } } public static String doHttpReq(String url, Map<String, String> params, String post) throws Exception { String encoding = "utf-8"; if(params != null && !params.isEmpty()) { StringBuilder sb = new StringBuilder(url); char c = '?'; for(Entry<String, String> entry : params.entrySet()) { sb.append(c); c = '&'; sb.append(URLEncoder.encode(entry.getKey(), encoding)); sb.append('='); sb.append(URLEncoder.encode(entry.getValue(), encoding)); } url = sb.toString(); } HttpURLConnection conn = null; InputStream is = null; byte[] body = (post != null ? post.getBytes(Const.stringCharsetUTF8) : null); try { conn = (HttpURLConnection)new URL(url).openConnection(); conn.setUseCaches(false); conn.setRequestProperty("Accept-Charset", encoding); conn.setRequestProperty("User-Agent", "jane"); conn.setConnectTimeout(HTTP_REQ_CONNECTION_TIMEOUT); conn.setReadTimeout(HTTP_REQ_CONNECTION_TIMEOUT); if(body != null) { conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "text/plain; charset=utf-8"); conn.setFixedLengthStreamingMode(body.length); try(OutputStream os = conn.getOutputStream()) { os.write(body); os.flush(); } } String ct = conn.getContentType(); if(ct != null) { Matcher mat = _patCharset.matcher(ct); if(mat.find()) { String cs = mat.group(1); if(Charset.isSupported(cs)) encoding = cs; } } is = conn.getInputStream(); byte[] buf = new byte[HTTP_RES_BUFFER_SIZE]; ByteArrayOutputStream bs = new ByteArrayOutputStream(HTTP_RES_BUFFER_SIZE); for(;;) { int n = is.read(buf); if(n < 0) break; bs.write(buf, 0, n); } return bs.toString(encoding); } finally { if(is != null) { try { is.close(); } catch(IOException e) { } } if(conn != null) conn.disconnect(); } } private Util() { } }