001/**   
002* @Title: FaceUtilits.java 
003* @Package net.gdface.utils 
004* @Description: guyadong 
005* @author guyadong   
006* @date 2014-10-21 上午10:51:32 
007* @version V1.0   
008*/
009package net.gdface.utils;
010
011import java.io.BufferedWriter;
012import java.io.ByteArrayInputStream;
013import java.io.ByteArrayOutputStream;
014import java.io.File;
015import java.io.FileInputStream;
016import java.io.FileOutputStream;
017import java.io.IOException;
018import java.io.InputStream;
019import java.io.Writer;
020import java.lang.reflect.MalformedParameterizedTypeException;
021import java.lang.reflect.ParameterizedType;
022import java.lang.reflect.Type;
023import java.net.MalformedURLException;
024import java.net.URI;
025import java.net.URISyntaxException;
026import java.net.URL;
027import java.nio.ByteBuffer;
028import java.nio.channels.FileChannel;
029import java.security.MessageDigest;
030import java.security.NoSuchAlgorithmException;
031import java.util.Collection;
032import java.util.Map;
033import java.util.Map.Entry;
034import java.util.TreeMap;
035import java.util.TreeSet;
036
037/**
038 * @author guyadong
039 *
040 */
041public class FaceUtilits {
042        private static final String MD5_REGEX = "^[a-fA-F0-9]{32}$";
043        private static final String HEX_REGEX  = "^([a-fA-F0-9]{2})+$";
044        /**
045         * 生成MD5校验码
046         * 
047         * @param source
048         * @return
049         */
050        static public byte[] getMD5(byte[] source) {
051                if (Judge.isNull(source)){
052                        return null;
053                }
054                try {
055                        MessageDigest md = MessageDigest.getInstance("MD5");
056                        return md.digest(source);
057                } catch (NoSuchAlgorithmException e) {
058                        throw new RuntimeException(e);
059                }
060        }
061        /**
062         * 返回buffer中所有字节(position~limit),不改变buffer状态
063         * @param buffer
064         * @return buffer 为 null 时返回 null 
065         */
066        public static final byte[] getBytesInBuffer(ByteBuffer buffer){
067                if(null == buffer){
068                        return null;
069                }
070                int pos = buffer.position();
071                try{
072                        byte[] bytes = new byte[buffer.remaining()];
073                        buffer.get(bytes);
074                        return bytes;
075                }finally{
076                        buffer.position(pos);
077                }
078        }
079        /**
080         * 生成MD5校验码
081         * @param source
082         * @return
083         * @see #getMD5(byte[])
084         */
085        static public ByteBuffer getMD5(ByteBuffer source) {
086                return null == source ?null:ByteBuffer.wrap(getMD5(getBytesInBuffer(source)));
087        }
088        /**
089         * 将16位byte[] 转换为32位的HEX格式的字符串String
090         * 
091         * @param buffer
092         * @return
093         */
094        static public String toHex(byte[] buffer) {
095                if (Judge.isNull(buffer)){
096                        return null;
097                }
098                StringBuffer sb = new StringBuffer(buffer.length * 2);
099                for (int i = 0; i < buffer.length; i++) {
100                        sb.append(Character.forDigit((buffer[i] & 240) >> 4, 16));
101                        sb.append(Character.forDigit(buffer[i] & 15, 16));
102                }
103                return sb.toString();
104        }
105        /** @see #toHex(byte[]) */
106        static public String toHex(ByteBuffer buffer) {
107                return toHex(getBytesInBuffer(buffer));  
108        }
109        /**
110         * 字符串验证器,根据正则表达式判断字符串是否为十六进制(HEX)字符串
111         * 输入为null或空或正则表达式不匹配则返回false
112         */
113        public boolean validHEX(String input){
114                return input != null && input.matches(HEX_REGEX);
115        }
116    public static byte[] hex2Bytes(String src){
117        if(null == src){
118                return null;
119        }
120        byte[] res = new byte[src.length()/2];  
121        char[] chs = src.toCharArray();  
122        int[] b = new int[2];  
123  
124        for(int i=0,c=0; i<chs.length; i+=2,c++){              
125            for(int j=0; j<2; j++){  
126                if(chs[i+j]>='0' && chs[i+j]<='9'){  
127                    b[j] = (chs[i+j]-'0');  
128                }else if(chs[i+j]>='A' && chs[i+j]<='F'){  
129                    b[j] = (chs[i+j]-'A'+10);  
130                }else if(chs[i+j]>='a' && chs[i+j]<='f'){  
131                    b[j] = (chs[i+j]-'a'+10);  
132                }  
133            }   
134              
135            b[0] = (b[0]&0x0f)<<4;  
136            b[1] = (b[1]&0x0f);  
137            res[c] = (byte) (b[0] | b[1]);  
138        }  
139          
140        return res;  
141    } 
142    public static ByteBuffer hex2ByteBuffer(String src){
143        return null == src?null:ByteBuffer.wrap(hex2Bytes(src));
144    }
145        /**
146         * 生成MD5校验码字符串
147         * 
148         * @param source
149         * @return
150         * @see #getMD5(byte[])
151         * @see #toHex(byte[])
152         */
153        static public String getMD5String(byte[] source) {
154                return toHex(getMD5(source));
155        }
156        /**
157         * 生成MD5校验码字符串
158         * 
159         * @param source
160         * @return
161         * @see #getMD5(byte[])
162         * @see #toHex(byte[])
163         */
164        static public String getMD5String(ByteBuffer source) {
165                return toHex(getMD5(source));
166        }
167        /**
168         * 判断是否为有效的MD5字符串
169         * @return
170         */
171        public static final boolean validMd5(String md5){
172                return null != md5 && md5.matches(MD5_REGEX);
173        }
174        /**
175         * 从{@link InputStream}读取字节数组<br>
176         * 当{@code in}为{@link FileInputStream}时,调用{@link #readBytes(FileInputStream)}(NIO方式)读取<br>
177         *  结束时会关闭{@link InputStream}
178         * @param in
179         * @return
180         * @throws IOException
181         * @throws IllegalArgumentException {@code in}为{@code null}
182         */
183        public static byte[] readBytes(InputStream in) throws IOException, IllegalArgumentException {
184                Assert.notNull(in, "in");
185                if(in instanceof FileInputStream){
186                        return readBytes((FileInputStream)in);
187                }
188                try {
189                        int buffSize = Math.max(in.available(), 1024*8);
190                        byte[] temp = new byte[buffSize];
191                        ByteArrayOutputStream out = new ByteArrayOutputStream(buffSize);
192                        int size = 0;
193                        while ((size = in.read(temp)) != -1) {
194                                out.write(temp, 0, size);
195                        }
196                        return  out.toByteArray();
197                } finally {
198                        in.close();
199                }
200        }
201        
202        /**
203         * NIO方式从{@link FileInputStream}读取字节数组<br>
204         *  结束时会关闭{@link InputStream}
205         * @param fin {@link FileInputStream}
206         * @return 返回读取的字节数 当{@code fin}为null时返回null;
207         * @throws IOException
208         */
209        public static byte[] readBytes(FileInputStream fin) throws IOException {
210                Assert.notNull(fin, "fin");
211                FileChannel fc = fin.getChannel();
212                try {
213                        ByteBuffer bb = ByteBuffer.allocate((int) fc.size());
214                        fc.read(bb);
215                        bb.flip();
216                        return bb.array();
217                } finally {
218                        if (null != fc){
219                                fc.close();
220                        }
221                        fin.close();
222                }
223        }
224        /**
225         * 将对象转换为InputStream<br>
226         * 类型可以是byte[],{@link ByteBuffer},{@link InputStream},{@link String}(base64编码),{@link File},{@link URL},{@link URI},否则抛出RuntimeException<br>
227         * 
228         * @param src
229         *            获取InputStream的源对象
230         * @return 返回获取的InputStream对象,src为null失败返回null或抛出异常
231         * @throws IOException
232         * @throws IllegalArgumentException 无法从{@code src}获取{@link InputStream}
233         */
234        public static <T> InputStream getInputStream(T src) throws IOException, IllegalArgumentException {
235                Assert.notNull(src, "src");
236                if (src instanceof InputStream){
237                        return (InputStream) src;
238                }else if (src instanceof String) {
239                        return new ByteArrayInputStream(Base64Utils.decode(((String) src)));
240                } else if (src instanceof byte[]) {
241                        return new ByteArrayInputStream((byte[]) src);
242                } else if (src instanceof ByteBuffer) {
243                        return new ByteArrayInputStream(getBytesInBuffer((ByteBuffer) src));
244                } else if (src instanceof File) {
245                        return new FileInputStream((File) src);
246                } else if (src instanceof URL) {
247                        return ((URL) src).openStream();
248                } else if (src instanceof URI) {
249                        return ((URI) src).toURL().openStream();
250                } else{
251                        throw new IllegalArgumentException(String.format("Can't get inputstream from [%s]", src.getClass()
252                                        .getCanonicalName()));
253                }
254        }
255
256        /**
257         * 将数据对象{@code src}转换为字节数组(byte[])<br>
258         * {@code src}的数据类型可以是byte[],{@link InputStream},{@link ByteBuffer},{@link String}(base64编码),{@link File},{@link URL},{@link URI}
259         * 否则抛出{@link IllegalArgumentException}<br>
260         * 对象转换为InputStream或byte[]时,可能会抛出{@link IOException}
261         * 
262         * 当{@code src}为{@link File}或{@link FileInputStream}时,使用NIO方式({@link #readBytes(FileInputStream)})读取
263         * 
264         * @param src
265         *            获取byte[]的源对象
266         * @return 返回字节数组,参数为{@code null}或类型不对则抛出异常
267         * @throws IOException
268         * @throws IllegalArgumentException {@code src}为{@code null}或无法从{@code src}获取{@link InputStream}
269         * @see #readBytes(InputStream)
270         * @see #readBytes(FileInputStream)
271         * @see #getInputStream(Object)
272         * @see Base64Utils#decode(String)
273         */
274        static public final <T> byte[] getBytes(T src) throws IOException, IllegalArgumentException {
275                Assert.notNull(src, "src");             
276                if (src instanceof byte[]) {
277                        return (byte[]) src;
278                } else if (src instanceof String) {
279                        return Base64Utils.decode(((String) src));
280                } else if (src instanceof ByteBuffer) {
281                        return getBytesInBuffer((ByteBuffer)src);
282                } else if (src instanceof FileInputStream){
283                        return readBytes((FileInputStream)src);
284                }else if (src instanceof File){
285                        return readBytes(new FileInputStream((File)src));
286                }else {
287                        return readBytes(getInputStream(src));
288                }
289        }
290        
291        /**
292         * 调用 {@link #getBytes(Object)}返回非空字节数组<br>
293         * 如果返回{@code null}或空字节数组,则抛出{@link IOException}<br>
294         * @param src
295         * @return
296         * @throws IOException
297         * @throws IllegalArgumentException
298         * @see #getBytes(Object)
299         */
300        static public final <T> byte[] getBytesNotEmpty(T src) throws IOException, IllegalArgumentException {
301                byte[] imgData = getBytes(src);
302                if (Judge.isEmpty(imgData)){
303                        throw new IOException(String.format("return null or zero length from %s", src.getClass()
304                                        .getSimpleName()));
305                }
306                return imgData;
307        }
308
309        /**
310         * 将数据对象src转换为{@link ByteBuffer}
311         * @param src
312         * @return
313         * @throws IOException
314         * @throws IllegalArgumentException
315         * @see #getBytes(Object)
316         */
317        static public final <T> ByteBuffer getByteBuffer(T src) throws IOException, IllegalArgumentException {
318                return ByteBuffer.wrap(getBytes(src));
319        }
320        /**
321         * 调用 {@link #getByteBuffer(Object)}返回非空{@link ByteBuffer}<br>
322         * 如果返回{@code null}或空,则抛出{@link IOException}<br>
323         * @param src
324         * @return
325         * @throws IOException
326         * @throws IllegalArgumentException
327         */
328        static public final <T> ByteBuffer getByteBufferNotEmpty(T src) throws IOException, IllegalArgumentException {
329                return ByteBuffer.wrap(getBytesNotEmpty(src));
330        }
331        /**
332         * 将图片数据保存在folder指定的文件夹下,文件名用图片的md5校验码命名,自动判断文件后缀<br>
333         * 
334         * @param img
335         *            图像数据
336         * @param folder
337         *            文件保存的位置
338         * @return 返回保存的文件名,如果{@code img}中无法获取格式名,则视为无效数据,不保存,返回null;
339         * @throws IOException
340         *             调用{@link FaceUtilitsX#getFormatName(byte[])}获取图像格式名称出错或其他IO异常
341         * @throws IllegalArgumentException
342         *             {@code data}为null或空时
343         * @see #saveBytes(byte[], File, boolean)
344         */
345        public static File saveImageAutoName(byte[] img, File folder) throws IOException, IllegalArgumentException {
346                Assert.notEmpty(img, "img");
347                File file = new File(folder, getMD5String(img) + "."+FaceUtilitsX.getFormatName(img).toLowerCase());
348                return saveBytes(img,file,file.exists()&&file.isFile()&&0==file.length());              
349        }
350
351        /**
352         * @param img
353         * @param folder
354         *            文件保存位置
355         * @throws IOException
356         *             数据非可识别的图像格式或其他IO异常
357         * @throws IllegalArgumentException
358         *             {@code img}为null
359         * @see #saveImageAutoName(byte[], File)
360         * @return 返回保存的文件,如果从{@code img}中读取的数据为空返回null
361         */
362        public static File saveImage(InputStream img, File folder) throws IOException, IllegalArgumentException {
363                Assert.notNull(img, "img");
364                byte[] imgData = readBytes(img);
365                return Judge.isEmpty(imgData)?null:saveImageAutoName(imgData,folder);
366        }
367        
368        /**
369         * 将{@code URL}字符串转换为{@code URI}对象<br>
370         * 在转换过程中会将自动对不符合URI规范的字符进行编码,<br>
371         * 在转换过程中先从字符串生成{@code URL}对象,如果{@code String}不能转换成URL对象,则抛出异常
372         * @param urlStr
373         * @return
374         * @throws MalformedURLException
375         */
376        public static URI createURI(String urlStr) throws MalformedURLException{
377                try {
378                        return new URI(urlStr);
379                } catch (URISyntaxException e) {
380                        try {
381                                URL url=new URL(urlStr);
382                                return new URI(url.getProtocol(),url.getUserInfo(),url.getHost(),url.getPort(),url.getPath(),url.getQuery(),url.getRef());
383                        } catch (URISyntaxException e1) {
384                                throw new RuntimeException(e1);
385                        }
386                }
387        }
388
389        /**
390         * NIO方式将{@code data}数据保存在{@code file}指定的文件中<br>
391         * 如果{@code file}所在文件夹不存在,则会自动创建所有的文件夹<br>
392         * @param data
393         * @param file 文件保存的位置
394         * @param overwrite 同名文件存在时是否覆盖
395         * @return 返回保存的文件名
396         * @throws IOException {@code file}存在但不是文件或其他IO异常
397         * @throws IllegalArgumentException {@code data}为null时
398         */
399        public static File saveBytes(byte[] data, File file, boolean overwrite) throws IOException,
400                        IllegalArgumentException {
401                Assert.notNull(data, "data");
402                FileOutputStream out = null;
403                FileChannel fc = null;
404                try {
405                        File folder = file.getParentFile();
406                        if (!folder.exists()){
407                                folder.mkdirs();
408                        }
409                        long free = folder.getFreeSpace()>>20;//可用磁盘空间(MB)
410                        if(free<10){
411                                throw new IOException(String.format("DISK ALMOST FULL(磁盘空间不足) FREE %dMB,%s",free,folder.getAbsolutePath()));
412                        }
413                        if (!file.exists() || !file.isFile() || overwrite) {
414                                out = new FileOutputStream(file);
415                                fc = out.getChannel();
416                                ByteBuffer bb = ByteBuffer.wrap(data);
417                                fc.write(bb);
418                        }
419                        return file;
420                } finally {
421                        if (null != fc){
422                                fc.close();
423                        }
424                        if (null != out){
425                                out.close();
426                        }
427                }
428        }
429        
430        /**
431         * 如果无法获取泛型参数对象,返回null
432         * @param clazz
433         * @return
434         * @see #getParameterizedType(Class)
435         */
436        public static  Class<?>[] getParameterizedTypeNoThrow(Class<?>clazz) {
437                try{
438                Class<?>[] types=getParameterizedType(clazz);
439                return types;
440                }catch(Exception e){
441                        return null;
442                }
443        }
444        /**
445         * 返回{@code clazz}泛型超类的参数对象<br>
446         * 如果超类不是泛型对象,则抛出{@link IllegalArgumentException}<br>
447         * @param clazz
448         * @return
449         * @throws MalformedParameterizedTypeException 超类不是泛型类
450         * @throws IllegalArgumentException 无法获取实际泛型参数对象类型
451         */
452        public static  Class<?>[] getParameterizedType(Class<?>clazz) throws MalformedParameterizedTypeException, IllegalArgumentException{
453                Type partype = clazz.getGenericSuperclass();            
454                if(!(partype instanceof ParameterizedType)){
455                        //超类不是泛型
456                        throw new IllegalArgumentException(String.format("superclass of %s  not ParameterizedType(超类不是泛型类)",clazz.getName()));
457                }
458                Type[] types = ((ParameterizedType) partype).getActualTypeArguments();
459                if(!(types[0] instanceof Class<?>)){
460                        System.err.print("cant'not get class for ParameterizedType (无法获取实际泛型参数对象类型(Class))");
461                        throw new MalformedParameterizedTypeException();
462                } 
463                Class<?>[] paramClass=new Class<?>[types.length];               
464                for(int i=0;i<paramClass.length;i++){
465                        paramClass[i]=(Class<?>) types[i];
466                }
467                return paramClass;              
468        }
469
470        public final static Throwable getCause(Throwable e) {
471                return e==null?null:(e.getCause()==null?e:e.getCause());
472        }
473
474        /**
475         * 以递归方式返回被{@code shellClass}多层封装的异常<br>
476         * @param e
477         * @param shellClass 封装异常的类
478         * @return
479         */
480        public static final Throwable stripThrowableShell(Throwable e, Class<? extends Throwable> shellClass){
481                if(!Judge.hasNull(e,e.getCause())&&e.getClass()==shellClass){
482                        return stripThrowableShell(e.getCause(), shellClass);
483                }
484                return e;
485        }
486
487        /**
488         * 对{@link Map}中元素以key排序后,每行以{key}={value}形式输出到{@link Writer}<br>
489         * map为空或null时则不向writer写入任何内容
490         * @param map
491         * @param writer 为null抛出{@link IllegalArgumentException}
492         * @param lineSeparator 换行符,为null则使用系统默认的换行符(windows \n linux \r\n)
493         * @throws IOException
494         */
495        public static  void storeSortedMap(Map<String,String> map,Writer writer, String lineSeparator)  throws IOException {
496                Assert.notNull(writer, "writer");
497                TreeMap<String, String> sortedMap = new TreeMap<String,String>();
498                if(null!=map){
499                        sortedMap.putAll(map);
500                }
501                BufferedWriter bw=(writer instanceof BufferedWriter)?(BufferedWriter)writer
502                                : new BufferedWriter(writer);
503                for (Entry<String,String> e:sortedMap.entrySet()) {
504                        bw.write(e.getKey() + "=" + e.getValue());
505                        if(null==lineSeparator){
506                                bw.newLine();
507                        }else{
508                                bw.write("\n");
509                        }
510                }
511                bw.flush();
512        }
513
514        /**
515         * 对 {@link Collection}中元素排序后(去除重复),元素分行输出到{@link Writer}<br>
516         * collection为空或null时则不向writer写入任何内容
517         * @param collection
518         * @param writer 为null抛出{@link IllegalArgumentException}
519         * @param lineSeparator 换行符,为null则使用系统默认的换行符(windows \n linux \r\n)
520         * @throws IOException
521         */
522        public static  void storeSortedSet(Collection<String> collection,Writer writer, String lineSeparator)  throws IOException {
523                Assert.notNull(writer, "writer");
524                TreeSet<String> sortedSet = new TreeSet<String>();
525                if(null!=collection){
526                        sortedSet.addAll(collection);
527                }
528                BufferedWriter bw=(writer instanceof BufferedWriter)?(BufferedWriter)writer
529                                : new BufferedWriter(writer);
530                for (String e:sortedSet) {                      
531                        bw.write(e);
532                        if(null==lineSeparator){
533                                bw.newLine();
534                        }else{
535                                bw.write("\n");
536                        }
537                }
538                bw.flush();
539        }
540        
541        /**
542         * 比较两个Map是否相等
543         * @param m1
544         * @param m2
545         * @return
546         */
547        public static <K,V>boolean equals(Map<K,V> m1,Map<K,V> m2){
548                if(m1==m2){
549                        return true;
550                }
551                if(null ==m1 || null ==m2){
552                        return false;
553                }
554                if(m1.size() != m2.size()){
555                        return false;
556                }
557                for(Entry<K, V> entry:m1.entrySet()){
558                        K key = entry.getKey();
559                        if(!m2.containsKey(key)){
560                                return false;
561                        }
562                        V v1 = entry.getValue();
563                        V v2 = m2.get(key);
564                        if(v1 ==v2 ) {
565                                continue;
566                        }
567                        if(null ==v1 || null ==v2){
568                                return false;
569                        }
570                        if(!v1.equals(v2)){
571                                return false;
572                        }
573                }
574                return true;
575        }
576}