/*
 * File   : $Source: /usr/local/cvs/opencms/src/org/opencms/file/wrapper/CmsResourceWrapperUtils.java,v $
 * Date   : $Date: 2008-02-27 12:05:29 $
 * Version: $Revision: 1.5 $
 *
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software GmbH, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.file.wrapper;

import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProperty;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.file.CmsResource;
import org.opencms.file.types.CmsResourceTypePlain;
import org.opencms.i18n.CmsEncoder;
import org.opencms.main.CmsException;
import org.opencms.util.CmsStringUtil;
import org.opencms.workplace.commons.CmsPropertyAdvanced;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Helper class with several methods used by different implementations of the
 * interface {@link I_CmsResourceWrapper}.<p>
 * 
 * It provides methods to add or remove file extensions to resources, to handle
 * creating and writing property files and to add the byte order mask to UTF-8
 * byte contents.<p>
 * 
 * @author Peter Bonrad
 * 
 * @version $Revision: 1.5 $
 * 
 * @since 6.2.4
 */
public final class CmsResourceWrapperUtils {

    /** The extension to use for the property file. */
    public static final String EXTENSION_PROPERTIES = "properties";

    /** The prefix used for a shared property entry. */
    public static final String SUFFIX_PROP_INDIVIDUAL = ".i";

    /** The prefix used for a shared property entry. */
    public static final String SUFFIX_PROP_SHARED = ".s";

    /** The UTF-8 bytes to add to the beginning of text contents. */
    public static final byte[] UTF8_MARKER = new byte[] {(byte)0xEF, (byte)0xBB, (byte)0xBF};

    /** Pattern to use for incoming strings before storing in OpenCms. */
    private static final Pattern PATTERN_UNESCAPE = Pattern.compile("\\\\([^ntru\n\r])");

    /**
     * Hide utility class constructor.<p>
     */
    private CmsResourceWrapperUtils() {

        // noop
    }

    /**
     * Adds a file extension to the resource name.<p>
     * 
     * If the file with the new extension already exists, an index count will be
     * added before the final extension.<p>
     * 
     * For example: <code>index.html.1.jsp</code>.<p>
     * 
     * @see #removeFileExtension(CmsObject, String, String)
     * 
     * @param cms the actual CmsObject
     * @param resourcename the name of the resource where to add the file extension
     * @param extension the extension to add
     * 
     * @return the resource name with the added file extension
     */
    public static String addFileExtension(CmsObject cms, String resourcename, String extension) {

        if (!extension.startsWith(".")) {
            extension = "." + extension;
        }

        if (!resourcename.endsWith(extension)) {
            String name = resourcename + extension;
            int count = 0;
            while (cms.existsResource(name)) {
                count++;
                name = resourcename + "." + count + extension;
            }

            return name;
        }

        return resourcename;
    }

    /**
     * Adds the UTF-8 marker add the beginning of the byte array.<p>
     * 
     * @param content the byte array where to add the UTF-8 marker
     * 
     * @return the byte with the added UTF-8 marker at the beginning
     */
    public static byte[] addUtf8Marker(byte[] content) {

        if ((content != null)
            && (content.length >= 3)
            && (content[0] == UTF8_MARKER[0])
            && (content[1] == UTF8_MARKER[1])
            && (content[2] == UTF8_MARKER[2])) {
            return content;
        }

        if (content == null) {
            content = new byte[0];
        }

        byte[] ret = new byte[UTF8_MARKER.length + content.length];

        System.arraycopy(UTF8_MARKER, 0, ret, 0, UTF8_MARKER.length);
        System.arraycopy(content, 0, ret, UTF8_MARKER.length, content.length);

        return ret;
    }

    /**
     * Creates a virtual CmsFile with the individual and shared properties as content.<p>
     * 
     * For example looks like this:<br/>
     * Title.i=The title of the resource set as individual property<br/>
     * Title.s=The title of the resource set as shared property<br/>
     * 
     * @see #writePropertyFile(CmsObject, String, byte[])
     * 
     * @param cms the initialized CmsObject
     * @param res the resource where to read the properties from
     * @param path the full path to set for the created property file 
     * 
     * @return the created CmsFile with the individual and shared properties as the content
     * 
     * @throws CmsException if something goes wrong
     */
    public static CmsFile createPropertyFile(CmsObject cms, CmsResource res, String path) throws CmsException {

        StringBuffer content = new StringBuffer();

        // header
        content.append("# Properties for resource ");
        content.append(res.getRootPath());
        content.append("\n");

        content.append("#\n");
        content.append("# ${property_name}.i : individual property\n");
        content.append("# ${property_name}.s :     shared property\n\n");

        List propertyDef = cms.readAllPropertyDefinitions();
        Map activeProperties = CmsPropertyAdvanced.getPropertyMap(cms.readPropertyObjects(res, false));

        // iterate over all possible properties for the resource
        Iterator i = propertyDef.iterator();
        while (i.hasNext()) {
            CmsPropertyDefinition currentPropertyDef = (CmsPropertyDefinition)i.next();

            String propName = currentPropertyDef.getName();
            CmsProperty currentProperty = (CmsProperty)activeProperties.get(propName);
            if (currentProperty == null) {
                currentProperty = new CmsProperty();
            }

            String individualValue = currentProperty.getStructureValue();
            String sharedValue = currentProperty.getResourceValue();

            if (individualValue == null) {
                individualValue = "";
            }

            if (sharedValue == null) {
                sharedValue = "";
            }

            individualValue = escapeString(individualValue);
            sharedValue = escapeString(sharedValue);

            content.append(propName);
            content.append(SUFFIX_PROP_INDIVIDUAL);
            content.append("=");
            content.append(individualValue);
            content.append("\n");

            content.append(propName);
            content.append(SUFFIX_PROP_SHARED);
            content.append("=");
            content.append(sharedValue);
            content.append("\n\n");
        }

        CmsWrappedResource wrap = new CmsWrappedResource(res);
        wrap.setRootPath(addFileExtension(cms, path, EXTENSION_PROPERTIES));
        wrap.setTypeId(CmsResourceTypePlain.getStaticTypeId());
        wrap.setFolder(false);

        CmsFile ret = wrap.getFile();
        try {

            ret.setContents(content.toString().getBytes(CmsEncoder.ENCODING_UTF_8));
        } catch (UnsupportedEncodingException e) {

            // this will never happen since UTF-8 is always supported
            ret.setContents(content.toString().getBytes());
        }

        return ret;
    }

    /**
     * Removes an added file extension from the resource name.<p>
     * 
     * <ul>
     * <li>If there is only one extension, nothing will be removed.</li>
     * <li>If there are two extensions, the last one will be removed.</li>
     * <li>If there are more than two extensions the last one will be removed and
     * if then the last extension is a number, the extension with the number
     * will be removed too.</li>
     * </ul>
     * 
     * @see #addFileExtension(CmsObject, String, String)
     * 
     * @param cms the initialized CmsObject
     * @param resourcename the resource name to remove the file extension from
     * @param extension the extension to remove
     * 
     * @return the resource name without the removed file extension
     */
    public static String removeFileExtension(CmsObject cms, String resourcename, String extension) {

        if (resourcename.equals("")) {
            resourcename = "/";
        }

        // get the filename without the path
        String name = CmsResource.getName(resourcename);

        String[] tokens = name.split("\\.");
        String suffix = null;

        // check if there is more than one extension
        if (tokens.length > 2) {

            // check if last extension is "jsp"
            if (extension.equalsIgnoreCase(tokens[tokens.length - 1])) {

                suffix = "." + extension;

                // check if there is another extension with a numeric index 
                if (tokens.length > 3) {

                    try {
                        int index = Integer.valueOf(tokens[tokens.length - 2]).intValue();

                        suffix = "." + index + suffix;
                    } catch (NumberFormatException ex) {
                        // noop
                    }
                }
            }
        } else if (tokens.length == 2) {

            // there is only one extension!! 
            // only remove the last extension, if the resource without the extension exists
            // and the extension fits
            if ((cms.existsResource(CmsResource.getFolderPath(resourcename) + tokens[0]))
                && (extension.equals(tokens[1]))) {
                suffix = "." + tokens[1];
            }
        }

        if (suffix != null) {

            String path = resourcename.substring(0, resourcename.length() - suffix.length());
            return path;
        }

        return resourcename;
    }

    /**
     * Removes the UTF-8 marker from the beginning of the byte array.<p>
     * 
     * @param content the byte array where to remove the UTF-8 marker
     * 
     * @return the byte with the removed UTF-8 marker at the beginning
     */
    public static byte[] removeUtf8Marker(byte[] content) {

        if ((content != null)
            && (content.length >= 3)
            && (content[0] == UTF8_MARKER[0])
            && (content[1] == UTF8_MARKER[1])
            && (content[2] == UTF8_MARKER[2])) {

            byte[] ret = new byte[content.length - UTF8_MARKER.length];
            System.arraycopy(content, 3, ret, 0, content.length - UTF8_MARKER.length);

            return ret;
        }

        return content;
    }

    /**
     * Takes the content which should be formatted as a property file and set them
     * as properties to the resource.<p>
     * 
     * @see #createPropertyFile(CmsObject, CmsResource, String)
     * 
     * @param cms the initialized CmsObject
     * @param resourcename the name of the resource where to set the properties
     * @param content the properties to set (formatted as a property file)
     * 
     * @throws CmsException if something goes wrong
     */
    public static void writePropertyFile(CmsObject cms, String resourcename, byte[] content) throws CmsException {

        Properties properties = new Properties();
        try {
            String props = CmsEncoder.createString(content, CmsEncoder.ENCODING_UTF_8);
            props = unescapeString(props);
            props = CmsEncoder.encodeJavaEntities(props, CmsEncoder.ENCODING_ISO_8859_1);
            byte[] modContent = props.getBytes(CmsEncoder.ENCODING_ISO_8859_1);

            properties.load(new ByteArrayInputStream(modContent));

            List propList = new ArrayList();
            Iterator it = properties.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry e = (Map.Entry)it.next();
                String key = (String)e.getKey();
                String value = (String)e.getValue();

                if (key.endsWith(SUFFIX_PROP_SHARED)) {
                    propList.add(new CmsProperty(
                        key.substring(0, key.length() - SUFFIX_PROP_SHARED.length()),
                        null,
                        value));
                } else if (key.endsWith(SUFFIX_PROP_INDIVIDUAL)) {
                    propList.add(new CmsProperty(
                        key.substring(0, key.length() - SUFFIX_PROP_INDIVIDUAL.length()),
                        value,
                        null));
                }
            }

            cms.writePropertyObjects(resourcename, propList);
        } catch (IOException e) {
            // noop
        }

    }

    /**
     * Escapes the value of a property in OpenCms to be displayed 
     * correctly in a property file.<p>
     * 
     * Mainly handles all escaping sequences that start with a backslash.<p>
     * 
     * @see #unescapeString(String)
     * 
     * @param value the value with the string to be escaped
     * 
     * @return the escaped string
     */
    private static String escapeString(String value) {

        Map substitutions = new HashMap();
        substitutions.put("\n", "\\n");
        substitutions.put("\t", "\\t");
        substitutions.put("\r", "\\r");

        return CmsStringUtil.substitute(value, substitutions);
    }

    /**
     * Unescapes the value of a property in a property file to 
     * be saved correctly in OpenCms.<p>
     * 
     * Mainly handles all escaping sequences that start with a backslash.<p>
     * 
     * @see #escapeString(String)
     * 
     * @param value the value taken form the property file
     * 
     * @return the unescaped string value
     */
    private static String unescapeString(String value) {

        Matcher matcher = PATTERN_UNESCAPE.matcher(value);
        return matcher.replaceAll("\\\\\\\\$1");
    }

}
