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.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(final Properties defaultValues )
058    {
059        super( defaultValues );
060    }
061
062    /**
063     *  {@inheritDoc}
064     */
065    @Override
066    public synchronized void load(final InputStream inStream ) throws IOException
067    {
068        // Load the file itself into a string
069        m_propertyString = FileUtil.readContents( inStream, StandardCharsets.ISO_8859_1.name() );
070
071        // Now load it into the properties object as normal
072        super.load( new ByteArrayInputStream( m_propertyString.getBytes(StandardCharsets.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(final 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(StandardCharsets.ISO_8859_1) ) );
088    }
089
090    /**
091     * {@inheritDoc}
092     */
093    @Override
094    public synchronized Object setProperty(final String key, final String value )
095    {
096        return put(key, value);
097    }
098
099    /**
100     * {@inheritDoc}
101     */
102    @Override
103    public synchronized void store(final OutputStream out, final String comments ) throws IOException
104    {
105        final byte[] bytes = m_propertyString.getBytes(StandardCharsets.ISO_8859_1.name());
106        FileUtil.copyContents( new ByteArrayInputStream( bytes ), out );
107        out.flush();
108    }
109
110    /**
111     * {@inheritDoc}
112     */
113    @Override
114    public synchronized Object put(final Object arg0, final 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(final Map< ? , ? > arg0 )
128    {
129        // Shove all of the entries into the property string
130        for (final Entry<?, ?> value : arg0.entrySet()) {
131            @SuppressWarnings("unchecked") final Entry<Object, Object> entry = (Entry<Object, Object>) value;
132            writeProperty(entry.getKey(), entry.getValue());
133        }
134
135        // Call the superclass method
136        super.putAll(arg0);
137    }
138
139    /**
140     * {@inheritDoc}
141     */
142    @Override
143    public synchronized Object remove(final Object key )
144    {
145        // Remove from the property string
146        deleteProperty( key );
147
148        // Call the superclass method
149        return super.remove(key);
150    }
151
152    /**
153     * {@inheritDoc}
154     */
155    @Override
156    public synchronized String toString()
157    {
158        return m_propertyString;
159    }
160
161    private void deleteProperty(final Object arg0 )
162    {
163        // Get key and value
164        if ( arg0 == null )
165        {
166            throw new IllegalArgumentException( "Key cannot be null." );
167        }
168        final String key = arg0.toString();
169
170        // Iterate through each line and replace anything matching our key
171        int idx = 0;
172        while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) )
173        {
174            final int prevret = m_propertyString.lastIndexOf( "\n", idx );
175            if ( prevret != -1 )
176            {
177                // Commented lines are skipped
178                if ( m_propertyString.charAt( prevret + 1 ) == '#' )
179                {
180                    idx += key.length();
181                    continue;
182                }
183            }
184
185            // If "=" present, delete the entire line
186            final int eqsign = m_propertyString.indexOf( "=", idx );
187            if ( eqsign != -1 )
188            {
189                final int ret = m_propertyString.indexOf( "\n", eqsign );
190                m_propertyString = TextUtil.replaceString( m_propertyString, prevret, ret, "" );
191                return;
192            }
193        }
194    }
195
196    private void writeProperty(final Object arg0, Object arg1 )
197    {
198        // Get key and value
199        if ( arg0 == null )
200        {
201            throw new IllegalArgumentException( "Key cannot be null." );
202        }
203        if ( arg1 == null )
204        {
205            arg1 = "";
206        }
207        final String key = arg0.toString();
208        final String value = TextUtil.native2Ascii( arg1.toString() );
209
210        // Iterate through each line and replace anything matching our key
211        int idx = 0;
212        while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) )
213        {
214            final int prevret = m_propertyString.lastIndexOf( "\n", idx );
215            if ( prevret != -1 )
216            {
217                // Commented lines are skipped
218                if ( m_propertyString.charAt( prevret + 1 ) == '#' )
219                {
220                    idx += key.length();
221                    continue;
222                }
223            }
224
225            // If "=" present, replace everything in line after it
226            final int eqsign = m_propertyString.indexOf( "=", idx );
227            if ( eqsign != -1 )
228            {
229                int ret = m_propertyString.indexOf( "\n", eqsign );
230                if ( ret == -1 )
231                {
232                    ret = m_propertyString.length();
233                }
234                m_propertyString = TextUtil.replaceString( m_propertyString, eqsign + 1, ret, value );
235                return;
236            }
237        }
238
239        // If it was not found, we'll add it to the end.
240        m_propertyString += "\n" + key + " = " + value + "\n";
241    }
242
243}