/*
 * $Header: /home/cvspublic/jakarta-slide/src/stores/org/apache/slide/store/txfile/PortableIdMapper.java,v 1.1 2004/12/19 00:17:51 ozeigermann Exp $
 * $Revision: 1.1 $
 * $Date: 2004/12/19 00:17:51 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation 
 *
 * 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 org.apache.slide.store.txfile;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.transaction.file.ResourceIdToPathMapper;

/**
 * This mapper is an option to the default
 * {@link org.apache.commons.transaction.file.URLEncodeIdMapper}because URL
 * encoding seemed too aggressive, particularly for a large content store with
 * many, deeply nested paths.
 * 
 * <p>
 * That storage technique would result in many, many files in a single directory
 * which would likely compromise performance and may lead to file limits in some
 * filesystems.
 * </p>
 * 
 * <p>
 * Portable path encoding aims to be a portable solution (can be moved from
 * Windows -> Linux -> MacOSX -> Windows Japanese -> etc. without modification)
 * and as such, does not write any non-ascii characters for any path. it may be
 * sufficient to avoid only non-latin characters, but to be safe, we encode any
 * path containing non-ascii characters.
 * </p>
 * 
 * <p>
 * Note that this encoding technique breaks apart the path based on the
 * File.separator and then recombines the encoded result to maintain the same
 * directory structure.
 * </p>
 * 
 */
public class PortableIdMapper implements ResourceIdToPathMapper {

    public static final int MAX_PORTABLE_FILENAME = 128;

    /**
     * MD5 message digest provider.
     */
    protected static java.security.MessageDigest md5Helper;

    private static final boolean isPortableChar(char c) {
        return (Character.UnicodeBlock.of(c) == Character.UnicodeBlock.BASIC_LATIN);
    }

    private static final boolean isPortableString(String str) {
        for (int i = 0; i < str.length(); i++) {
            if (!isPortableChar(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    private static final String makeStringPortable(String str) {
        if (!isPortableString(str)) {
            try {
                // encode utf-8 chars
                str = new String(Base64.encodeBase64(str.getBytes("UTF-8")), "ASCII");
                // make sure / is encoded
                str = URLEncoder.encode(str);
            } catch (UnsupportedEncodingException e) {
            }
        }

        return str;
    }

    private static final String makeFilePortable(String file) {
        String ret = file;

        if (!isPortableString(file)) {
            // we break on . and encode each part separately
            ret = "";

            int s = 0;
            int e = s;
            while ((e = file.indexOf('.', s)) != -1) {
                ret += makeStringPortable(file.substring(s, e)) + '.';
                s = e + 1;
            }
            ret += makeStringPortable(file.substring(s));
        }

        // We ran into some issues with Windows and file lengths, so
        // we cap the length at 128. Note that this does not affect
        // the actual length of the filename as used from the webdav
        // interface, because that will be stored in the metadata.
        if (ret.length() >= MAX_PORTABLE_FILENAME) {
            if (md5Helper == null) {
                // initialize the MD5 MessageDigest
                try {
                    md5Helper = java.security.MessageDigest.getInstance("MD5");
                } catch (java.security.NoSuchAlgorithmException e) {
                }
            }
            if (md5Helper != null) {
                try {
                    // truncate and make unique with MD5. note that using >=
                    // for the check, the only MAX_PORTABLE_FILENAME character
                    // names will be truncated files.
                    String md5 = DigestUtils.md5Hex(md5Helper.digest(ret.getBytes("ASCII")));
//                    String md5 = md5Encoder.encode(md5Helper.digest(ret.getBytes("ASCII")));
                    ret = ret.substring(0, MAX_PORTABLE_FILENAME - 33) + "_" + md5;
                } catch (UnsupportedEncodingException e) {
                }
            }
        }

        return ret;
    }

    private static final String makePathPortable(String path) {
        // we break on / and encode each part separately
        String ret = "";

        int s = 0;
        int e = s;
        while ((e = path.indexOf('/', s)) != -1) {
            ret += makeFilePortable(path.substring(s, e)) + '/';
            s = e + 1;
        }
        ret += makeFilePortable(path.substring(s));

        return ret;
    }

    public String getPathForId(Object resourceId) {
        String path = makePathPortable(resourceId.toString());
        return path;
    }
}