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    
015    package org.apache.tapestry5.internal.services.messages;
016    
017    import java.io.ByteArrayInputStream;
018    import java.io.IOException;
019    import java.io.InputStream;
020    import java.io.InputStreamReader;
021    import java.io.Reader;
022    import java.util.Map;
023    import java.util.Properties;
024    
025    import org.apache.tapestry5.ioc.Resource;
026    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
027    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
028    import org.apache.tapestry5.services.messages.PropertiesFileParser;
029    
030    public 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    }