    001// Copyright 2010 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007// http://www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014
    015package org.apache.tapestry5.internal.services.messages;
    016
    017import java.io.ByteArrayInputStream;
    018import java.io.IOException;
    019import java.io.InputStream;
    020import java.io.InputStreamReader;
    021import java.io.Reader;
    022import java.util.Map;
    023import java.util.Properties;
    024
    025import org.apache.tapestry5.ioc.Resource;
    026import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    027import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    028import org.apache.tapestry5.services.messages.PropertiesFileParser;
    029
    030public class PropertiesFileParserImpl implements PropertiesFileParser
    031{
    032    /**
    033     * Charset used when reading a properties file.
    034     */
    035    private static final String CHARSET = "UTF-8";
    036
    037    /**
    038     * Buffer size used when reading a properties file.
    039     */
    040    private static final int BUFFER_SIZE = 2000;
    041
    042    public Map<String, String> parsePropertiesFile(Resource resource) throws IOException
    043    {
    044        Map<String, String> result = CollectionFactory.newCaseInsensitiveMap();
    045
    046        Properties p = new Properties();
    047        InputStream is = null;
    048
    049        try
    050        {
    051
    052            is = readUTFStreamToEscapedASCII(resource.openStream());
    053
    054            // Ok, now we have the content read into memory as UTF-8, not ASCII.
    055
    056            p.load(is);
    057
    058            is.close();
    059
    060            is = null;
    061        }
    062        finally
    063        {
    064            InternalUtils.close(is);
    065        }
    066
    067        for (Map.Entry e : p.entrySet())
    068        {
    069            String key = e.getKey().toString();
    070
    071            String value = p.getProperty(key);
    072
    073            result.put(key, value);
    074        }
    075
    076        return result;
    077    }
    078
    079    /**
    080     * Reads a UTF-8 stream, performing a conversion to ASCII (i.e., ISO8859-1 encoding). Characters outside the normal
    081     * range for ISO8859-1 are converted to unicode escapes. In effect, Tapestry is performing native2ascii on the
    082     * files, on the fly.
    083     */
    084    private static InputStream readUTFStreamToEscapedASCII(InputStream is) throws IOException
    085    {
    086        Reader reader = new InputStreamReader(is, CHARSET);
    087
    088        StringBuilder builder = new StringBuilder(BUFFER_SIZE);
    089        char[] buffer = new char[BUFFER_SIZE];
    090
    091        while (true)
    092        {
    093            int length = reader.read(buffer);
    094
    095            if (length < 0)
    096                break;
    097
    098            for (int i = 0; i < length; i++)
    099            {
    100                char ch = buffer[i];
    101
    102                if (ch <= '\u007f')
    103                {
    104                    builder.append(ch);
    105                    continue;
    106                }
    107
    108                builder.append(String.format("\\u%04x", (int) ch));
    109            }
    110        }
    111
    112        reader.close();
    113
    114        byte[] resourceContent = builder.toString().getBytes();
    115
    116        return new ByteArrayInputStream(resourceContent);
    117    }
    118}