001/*
002 * Copyright (c) 2012, the Last.fm Java Project and Committers
003 * All rights reserved.
004 *
005 * Redistribution and use of this software in source and binary forms, with or without modification, are
006 * permitted provided that the following conditions are met:
007 *
008 * - Redistributions of source code must retain the above
009 *   copyright notice, this list of conditions and the
010 *   following disclaimer.
011 *
012 * - Redistributions in binary form must reproduce the above
013 *   copyright notice, this list of conditions and the
014 *   following disclaimer in the documentation and/or other
015 *   materials provided with the distribution.
016 *
017 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
018 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
019 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
020 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
021 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
022 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
023 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
024 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
025 */
026package de.umass.util;
027
028import java.io.UnsupportedEncodingException;
029import java.net.URLDecoder;
030import java.net.URLEncoder;
031import java.security.MessageDigest;
032import java.security.NoSuchAlgorithmException;
033import java.util.HashMap;
034import java.util.Map;
035import java.util.regex.Pattern;
036
037/**
038 * Utilitiy class with methods to calculate an md5 hash and to encode URLs.
039 *
040 * @author Janni Kovacs
041 */
042public final class StringUtilities {
043
044        private static MessageDigest digest;
045        private static Pattern MBID_PATTERN = Pattern
046                        .compile("^[0-9a-f]{8}\\-[0-9a-f]{4}\\-[0-9a-f]{4}\\-[0-9a-f]{4}\\-[0-9a-f]{12}$",
047                                        Pattern.CASE_INSENSITIVE);
048        private static final Pattern MD5_PATTERN = Pattern.compile("[a-fA-F0-9]{32}");
049
050        static {
051                try {
052                        digest = MessageDigest.getInstance("MD5");
053                } catch (NoSuchAlgorithmException e) {
054                        // better never happens
055                }
056        }
057
058        /**
059         * Returns a 32 chararacter hexadecimal representation of an MD5 hash of the given String.
060         * 
061         * @param s the String to hash
062         * @return the md5 hash
063         */
064        public static String md5(String s) {
065                try {
066                        byte[] bytes = digest.digest(s.getBytes("UTF-8"));
067                        StringBuilder b = new StringBuilder(32);
068                        for (byte aByte : bytes) {
069                                String hex = Integer.toHexString((int) aByte & 0xFF);
070                                if (hex.length() == 1)
071                                        b.append('0');
072                                b.append(hex);
073                        }
074                        return b.toString();
075                } catch (UnsupportedEncodingException e) {
076                        // utf-8 always available
077                }
078                return null;
079        }
080
081        /**
082         * URL Encodes the given String <code>s</code> using the UTF-8 character encoding.
083         *
084         * @param s a String
085         * @return url encoded string
086         */
087        public static String encode(String s) {
088                if(s == null)
089                        return null;
090                try {
091                        return URLEncoder.encode(s, "UTF-8");
092                } catch (UnsupportedEncodingException e) {
093                        // utf-8 always available
094                }
095                return null;
096        }
097
098        /**
099         * Decodes an URL encoded String <code>s</code> using the UTF-8 character encoding.
100         *
101         * @param s an encoded String
102         * @return the decoded String
103         */
104        public static String decode(String s) {
105                if(s == null)
106                        return null;
107                try {
108                        return URLDecoder.decode(s, "UTF-8");
109                } catch (UnsupportedEncodingException e) {
110                        // utf-8 always available
111                }
112                return null;
113        }
114
115        /**
116         * Checks if the supplied String <i>may</i> be a Musicbrainz ID. This method returns <code>true</code> for Strings that are
117         * exactly 36 characters long and match the MBID pattern <code>[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}</code>.
118         *
119         * @param nameOrMbid a possible MBID
120         * @return <code>true</code> if this String <i>may</i> be a MBID
121         */
122        public static boolean isMbid(String nameOrMbid) {
123                // example: bfcc6d75-a6a5-4bc6-8282-47aec8531818
124                return nameOrMbid != null && nameOrMbid.length() == 36 && MBID_PATTERN.matcher(nameOrMbid).matches();
125        }
126
127        /**
128         * Creates a Map out of an array with Strings.
129         *
130         * @param strings input strings, key-value alternating
131         * @return a parameter map
132         */
133        public static Map<String, String> map(String... strings) {
134                if (strings.length % 2 != 0)
135                        throw new IllegalArgumentException("strings.length % 2 != 0");
136                Map<String, String> mp = new HashMap<String, String>();
137                for (int i = 0; i < strings.length; i += 2) {
138                        mp.put(strings[i], strings[i + 1]);
139                }
140                return mp;
141        }
142
143        /**
144         * Strips all characters from a String, that might be invalid to be used in file names.
145         * By default <tt>: / \ < > | ? " *</tt> are all replaced by <tt>-</tt>.
146         * Note that this is no guarantee that the returned name will be definately valid.
147         *
148         * @param s the String to clean up
149         * @return the cleaned up String
150         */
151        public static String cleanUp(String s) {
152                return s.replaceAll("[*:/\\\\?|<>\"]", "-");
153        }
154
155        /**
156         * Tests if the given string <i>might</i> already be a 32-char md5 string.
157         *
158         * @param s String to test
159         * @return <code>true</code> if the given String might be a md5 string
160         */
161        public static boolean isMD5(String s) {
162                return s.length() == 32 && MD5_PATTERN.matcher(s).matches();
163        }
164
165        /**
166         * Converts a Last.fm boolean result string to a boolean.
167         *
168         * @param resultString A Last.fm boolean result string.
169         * @return <code>true</code> if the given String represents a true, <code>false</code> otherwise.
170         */
171        public static boolean convertToBoolean(String resultString) {
172                return "1".equals(resultString);
173        }
174
175        /**
176         * Converts from a boolean to a Last.fm boolean result string.
177         * 
178         * @param value A boolean value.
179         * @return A string representing a Last.fm boolean.
180         */
181        public static String convertFromBoolean(boolean value) {
182                if (value) {
183                        return "1";
184                } else {
185                        return "0";
186                }
187        }
188
189}