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.commons.Resource;
026import org.apache.tapestry5.commons.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}