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.nio.charset.StandardCharsets;
027import java.util.Iterator;
028import java.util.Map;
029import java.util.Map.Entry;
030import java.util.Properties;
031
032/**
033 * Extends {@link java.util.Properties} by providing support for comment
034 * preservation. When the properties are written to disk, previous
035 * comments present in the file are preserved.
036 * @since 2.4.22
037 */
038public class CommentedProperties extends Properties
039{
040    private static final long serialVersionUID = 8057284636436329669L;
041
042    private String m_propertyString;
043
044    /**
045     * @see java.util.Properties#Properties()
046     */
047    public CommentedProperties()
048    {
049        super();
050    }
051
052    /**
053     *  Creates new properties.
054     *
055     *  @param defaultValues A list of default values, which are used if in subsequent gets
056     *                       a key is not found.
057     */
058    public CommentedProperties(final Properties defaultValues )
059    {
060        super( defaultValues );
061    }
062
063    /**
064     *  {@inheritDoc}
065     */
066    @Override
067    public synchronized void load(final InputStream inStream ) throws IOException
068    {
069        // Load the file itself into a string
070        m_propertyString = FileUtil.readContents( inStream, StandardCharsets.ISO_8859_1.name() );
071
072        // Now load it into the properties object as normal
073        super.load( new ByteArrayInputStream( m_propertyString.getBytes(StandardCharsets.ISO_8859_1) ) );
074    }
075
076    /**
077     *  Loads properties from a file opened by a supplied Reader.
078     *  
079     *  @param in The reader to read properties from
080     *  @throws IOException in case something goes wrong.
081     */
082    @Override
083    public synchronized void load(final Reader in ) throws IOException
084    {
085        m_propertyString = FileUtil.readContents( in );
086
087        // Now load it into the properties object as normal
088        super.load( new ByteArrayInputStream( m_propertyString.getBytes(StandardCharsets.ISO_8859_1) ) );
089    }
090
091    /**
092     * {@inheritDoc}
093     */
094    @Override
095    public synchronized Object setProperty(final String key, final String value )
096    {
097        return put(key, value);
098    }
099
100    /**
101     * {@inheritDoc}
102     */
103    @Override
104    public synchronized void store(final OutputStream out, final String comments ) throws IOException
105    {
106        final byte[] bytes = m_propertyString.getBytes(StandardCharsets.ISO_8859_1.name());
107        FileUtil.copyContents( new ByteArrayInputStream( bytes ), out );
108        out.flush();
109    }
110
111    /**
112     * {@inheritDoc}
113     */
114    @Override
115    public synchronized Object put(final Object arg0, final Object arg1 )
116    {
117        // Write the property to the stored string
118        writeProperty( arg0, arg1 );
119
120        // Return the result of from the superclass properties object
121        return super.put(arg0, arg1);
122    }
123
124    /**
125     * {@inheritDoc}
126     */
127    @Override
128    public synchronized void putAll(final Map< ? , ? > arg0 )
129    {
130        // Shove all of the entries into the property string
131        for(final Iterator< ? > it = arg0.entrySet().iterator(); it.hasNext(); )
132        {
133            @SuppressWarnings("unchecked") final 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(final 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(final Object arg0 )
164    {
165        // Get key and value
166        if ( arg0 == null )
167        {
168            throw new IllegalArgumentException( "Key cannot be null." );
169        }
170        final 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            final 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            final int eqsign = m_propertyString.indexOf( "=", idx );
189            if ( eqsign != -1 )
190            {
191                final 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(final 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        final String key = arg0.toString();
210        final 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            final 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            final 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}