001/*
002 *  Copyright 2012 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.core.util;
017
018import java.io.UnsupportedEncodingException;
019import java.security.MessageDigest;
020import java.security.NoSuchAlgorithmException;
021import java.text.Normalizer;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Iterator;
025import java.util.List;
026import java.util.StringTokenizer;
027
028import org.apache.commons.codec.binary.Base64;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import org.ametys.runtime.i18n.I18nizableText;
033
034/**
035 * A collection of String management utility methods.
036 */
037public final class StringUtils
038{
039    private static final Logger __LOGGER = LoggerFactory.getLogger(StringUtils.class);
040
041    private static final long __DATA_SIZE_NEXT_LIMIT = 1024;
042    private static final List<String> __DATA_SIZE_KEYS = List.of(
043        "PLUGINS_CORE_UI_FORMAT_FILE_SIZE_NOT_ESCAPED_BYTES",
044        "PLUGINS_CORE_UI_FORMAT_FILE_SIZE_NOT_ESCAPED_KB",
045        "PLUGINS_CORE_UI_FORMAT_FILE_SIZE_NOT_ESCAPED_MB",
046        "PLUGINS_CORE_UI_FORMAT_FILE_SIZE_NOT_ESCAPED_GB",
047        "PLUGINS_CORE_UI_FORMAT_FILE_SIZE_NOT_ESCAPED_TB"
048    );
049    
050    private StringUtils()
051    {
052        // empty private constructor
053    }
054    
055    /**
056     * Extract String values from a comma seprated list.
057     * @param values the comma separated list
058     * @return a collection of String or an empty collection if string is null or empty.
059     */
060    public static Collection<String> stringToCollection(String values)
061    {
062        Collection<String> result = new ArrayList<>();
063        if (values != null && values.length() > 0)
064        {
065            // Explore the string list with a stringtokenizer with ','.
066            StringTokenizer stk = new StringTokenizer(values, ",");
067
068            while (stk.hasMoreTokens())
069            {
070                // Don't forget to trim
071                result.add(stk.nextToken().trim());
072            }
073        }
074
075        return result;
076    }
077
078    /**
079     * Extract String values from a comma seprated list.
080     * @param values the comma separated list
081     * @return an array of String
082     */
083    public static String[] stringToStringArray(String values)
084    {
085        Collection<String> coll = stringToCollection(values);
086        return coll.toArray(new String[coll.size()]);
087    }
088    
089    /**
090     * Generates a unique String key, based on System.currentTimeMillis()
091     * @return a unique String value
092     */
093    public static String generateKey()
094    {
095        long value;
096        
097        // Find a new value
098        synchronized (StringUtils.class)
099        {
100            value = System.currentTimeMillis();
101
102            try
103            {
104                Thread.sleep(15);
105            }
106            catch (InterruptedException e)
107            {
108                // does nothing, continue
109            }
110        }
111
112        // Convert it to a string using radix 36 (more compact)
113        String longString = Long.toString(value, Character.MAX_RADIX);
114    
115        return longString;
116    }
117    
118    /**
119     * Encrypt a password by using first MD5 Hash and base64 encoding.
120     * @param password The password to be encrypted.
121     * @return The password encrypted or null if the MD5 is not supported
122     */
123    public static String md5Base64(String password)
124    {
125        if (password == null)
126        {
127            return null;
128        }
129        
130        MessageDigest md5;
131        try
132        {
133            md5 = MessageDigest.getInstance("MD5");
134        }
135        catch (NoSuchAlgorithmException e)
136        {
137            // This error exception not be raised since MD5 is embedded in the JDK
138            __LOGGER.error("Cannot encode the password to md5Base64", e);
139            return null;
140        }
141        
142        // MD5-hash the password.
143        md5.reset();
144        try
145        {
146            md5.update(password.getBytes("UTF-8"));
147        }
148        catch (UnsupportedEncodingException e)
149        {
150            throw new IllegalStateException(e);
151        }
152        byte [] hash = md5.digest();
153        
154        // Base64-encode the result.
155        try
156        {
157            return new String(Base64.encodeBase64(hash), "UTF-8");
158        }
159        catch (UnsupportedEncodingException e)
160        {
161            throw new IllegalStateException(e);
162        }
163    }
164    
165    
166    /**
167     * Normalize string. Pass to lower case and remove Unicode accents and diacritics
168     * @param value the value to normalize
169     * @return the normalized value
170     */
171    public static String normalizeStringValue(String value)
172    {
173        return Normalizer.normalize(value.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", "");
174    }
175    
176    /**
177     * Transform a size to a readable size for data (bytes, KB, MB, etc.).
178     * @param size The size to transform
179     * @return An internationalized text with the size and the unit.
180     */
181    public static I18nizableText toReadableDataSize(Long size)
182    {
183        if (size == 1L)
184        {
185            return _createReadatableDataSize(size, "PLUGINS_CORE_UI_FORMAT_FILE_SIZE_NOT_ESCAPED_BYTE");
186        }
187        return _toReadableDataSize(size, __DATA_SIZE_KEYS.iterator());
188    }
189    
190    private static I18nizableText _toReadableDataSize(Long size, Iterator<String> keys)
191    {
192        String key = keys.next();
193        if (!keys.hasNext() || size < __DATA_SIZE_NEXT_LIMIT)
194        {
195            return _createReadatableDataSize(size, key);
196        }
197        return _toReadableDataSize(size / __DATA_SIZE_NEXT_LIMIT, keys);
198    }
199    
200    private static I18nizableText _createReadatableDataSize(Long size, String key)
201    {
202        return new I18nizableText("plugin.core-ui", key, List.of(size.toString()));
203    }
204}