001/* 
002    Licensed to the Apache Software Foundation (ASF) under one
003    or more contributor license agreements.  See the NOTICE file
004    distributed with this work for additional information
005    regarding copyright ownership.  The ASF licenses this file
006    to you under the Apache License, Version 2.0 (the
007    "License"); you may not use this file except in compliance
008    with the License.  You may obtain a copy of the License at
009
010       http://www.apache.org/licenses/LICENSE-2.0
011
012    Unless required by applicable law or agreed to in writing,
013    software distributed under the License is distributed on an
014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015    KIND, either express or implied.  See the License for the
016    specific language governing permissions and limitations
017    under the License.  
018 */
019package org.apache.wiki.util;
020
021import java.io.ByteArrayInputStream;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.OutputStream;
025import java.io.Reader;
026import java.util.Iterator;
027import java.util.Map;
028import java.util.Map.Entry;
029import java.util.Properties;
030
031/**
032 * Extends {@link java.util.Properties} by providing support for comment
033 * preservation. When the properties are written to disk, previous
034 * comments present in the file are preserved.
035 * @since 2.4.22
036 */
037public class CommentedProperties extends Properties
038{
039    private static final long serialVersionUID = 8057284636436329669L;
040
041    private String m_propertyString;
042
043    /**
044     * @see java.util.Properties#Properties()
045     */
046    public CommentedProperties()
047    {
048        super();
049    }
050
051    /**
052     *  Creates new properties.
053     *
054     *  @param defaultValues A list of default values, which are used if in subsequent gets
055     *                       a key is not found.
056     */
057    public CommentedProperties( Properties defaultValues )
058    {
059        super( defaultValues );
060    }
061
062    /**
063     *  {@inheritDoc}
064     */
065    @Override
066    public synchronized void load( InputStream inStream ) throws IOException
067    {
068        // Load the file itself into a string
069        m_propertyString = FileUtil.readContents( inStream, "ISO-8859-1" );
070
071        // Now load it into the properties object as normal
072        super.load( new ByteArrayInputStream( m_propertyString.getBytes("ISO-8859-1") ) );
073    }
074
075    /**
076     *  Loads properties from a file opened by a supplied Reader.
077     *  
078     *  @param in The reader to read properties from
079     *  @throws IOException in case something goes wrong.
080     */
081    @Override
082    public synchronized void load( Reader in ) throws IOException
083    {
084        m_propertyString = FileUtil.readContents( in );
085
086        // Now load it into the properties object as normal
087        super.load( new ByteArrayInputStream( m_propertyString.getBytes("ISO-8859-1") ) );
088    }
089
090    /**
091     * {@inheritDoc}
092     */
093    @Override
094    public synchronized Object setProperty( String key, String value )
095    {
096        return put(key, value);
097    }
098
099    /**
100     * {@inheritDoc}
101     */
102    @Override
103    public synchronized void store( OutputStream out, String comments ) throws IOException
104    {
105        byte[] bytes = m_propertyString.getBytes("ISO-8859-1");
106        FileUtil.copyContents( new ByteArrayInputStream( bytes ), out );
107        out.flush();
108    }
109
110    /**
111     * {@inheritDoc}
112     */
113    @Override
114    public synchronized Object put( Object arg0, Object arg1 )
115    {
116        // Write the property to the stored string
117        writeProperty( arg0, arg1 );
118
119        // Return the result of from the superclass properties object
120        return super.put(arg0, arg1);
121    }
122
123    /**
124     * {@inheritDoc}
125     */
126    @Override
127    public synchronized void putAll( Map< ? , ? > arg0 )
128    {
129        // Shove all of the entries into the property string
130        for( Iterator< ? > it = arg0.entrySet().iterator(); it.hasNext(); )
131        {
132            @SuppressWarnings("unchecked") 
133            Entry< Object, Object > entry = ( Entry< Object, Object > )it.next();
134            writeProperty( entry.getKey(), entry.getValue() );
135        }
136
137        // Call the superclass method
138        super.putAll(arg0);
139    }
140
141    /**
142     * {@inheritDoc}
143     */
144    @Override
145    public synchronized Object remove( Object key )
146    {
147        // Remove from the property string
148        deleteProperty( key );
149
150        // Call the superclass method
151        return super.remove(key);
152    }
153
154    /**
155     * {@inheritDoc}
156     */
157    @Override
158    public synchronized String toString()
159    {
160        return m_propertyString;
161    }
162
163    private void deleteProperty( Object arg0 )
164    {
165        // Get key and value
166        if ( arg0 == null )
167        {
168            throw new IllegalArgumentException( "Key cannot be null." );
169        }
170        String key = arg0.toString();
171
172        // Iterate through each line and replace anything matching our key
173        int idx = 0;
174        while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) )
175        {
176            int prevret = m_propertyString.lastIndexOf( "\n", idx );
177            if ( prevret != -1 )
178            {
179                // Commented lines are skipped
180                if ( m_propertyString.charAt( prevret + 1 ) == '#' )
181                {
182                    idx += key.length();
183                    continue;
184                }
185            }
186
187            // If "=" present, delete the entire line
188            int eqsign = m_propertyString.indexOf( "=", idx );
189            if ( eqsign != -1 )
190            {
191                int ret = m_propertyString.indexOf( "\n", eqsign );
192                m_propertyString = TextUtil.replaceString( m_propertyString, prevret, ret, "" );
193                return;
194            }
195        }
196    }
197
198    private void writeProperty( Object arg0, Object arg1 )
199    {
200        // Get key and value
201        if ( arg0 == null )
202        {
203            throw new IllegalArgumentException( "Key cannot be null." );
204        }
205        if ( arg1 == null )
206        {
207            arg1 = "";
208        }
209        String key = arg0.toString();
210        String value = TextUtil.native2Ascii( arg1.toString() );
211
212        // Iterate through each line and replace anything matching our key
213        int idx = 0;
214        while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) )
215        {
216            int prevret = m_propertyString.lastIndexOf( "\n", idx );
217            if ( prevret != -1 )
218            {
219                // Commented lines are skipped
220                if ( m_propertyString.charAt( prevret + 1 ) == '#' )
221                {
222                    idx += key.length();
223                    continue;
224                }
225            }
226
227            // If "=" present, replace everything in line after it
228            int eqsign = m_propertyString.indexOf( "=", idx );
229            if ( eqsign != -1 )
230            {
231                int ret = m_propertyString.indexOf( "\n", eqsign );
232                if ( ret == -1 )
233                {
234                    ret = m_propertyString.length();
235                }
236                m_propertyString = TextUtil.replaceString( m_propertyString, eqsign + 1, ret, value );
237                return;
238            }
239        }
240
241        // If it was not found, we'll add it to the end.
242        m_propertyString += "\n" + key + " = " + value + "\n";
243    }
244
245}