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     */
019    package org.apache.wiki.util;
020    
021    import java.io.ByteArrayInputStream;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.OutputStream;
025    import java.io.Reader;
026    import java.util.Iterator;
027    import java.util.Map;
028    import java.util.Map.Entry;
029    import 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     */
037    public 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        public synchronized void load( Reader in ) throws IOException
082        {
083            m_propertyString = FileUtil.readContents( in );
084    
085            // Now load it into the properties object as normal
086            super.load( new ByteArrayInputStream( m_propertyString.getBytes("ISO-8859-1") ) );
087        }
088    
089        /**
090         * {@inheritDoc}
091         */
092        @Override
093        public synchronized Object setProperty( String key, String value )
094        {
095            return put(key, value);
096        }
097    
098        /**
099         * {@inheritDoc}
100         */
101        @Override
102        public synchronized void store( OutputStream out, String comments ) throws IOException
103        {
104            byte[] bytes = m_propertyString.getBytes("ISO-8859-1");
105            FileUtil.copyContents( new ByteArrayInputStream( bytes ), out );
106            out.flush();
107        }
108    
109        /**
110         * {@inheritDoc}
111         */
112        @Override
113        public synchronized Object put( Object arg0, Object arg1 )
114        {
115            // Write the property to the stored string
116            writeProperty( arg0, arg1 );
117    
118            // Return the result of from the superclass properties object
119            return super.put(arg0, arg1);
120        }
121    
122        /**
123         * {@inheritDoc}
124         */
125        @SuppressWarnings("unchecked")
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                Entry entry = (Entry)it.next();
133                writeProperty( entry.getKey(), entry.getValue() );
134            }
135    
136            // Call the superclass method
137            super.putAll(arg0);
138        }
139    
140        /**
141         * {@inheritDoc}
142         */
143        @Override
144        public synchronized Object remove( Object key )
145        {
146            // Remove from the property string
147            deleteProperty( key );
148    
149            // Call the superclass method
150            return super.remove(key);
151        }
152    
153        /**
154         * {@inheritDoc}
155         */
156        @Override
157        public synchronized String toString()
158        {
159            return m_propertyString;
160        }
161    
162        private void deleteProperty( Object arg0 )
163        {
164            // Get key and value
165            if ( arg0 == null )
166            {
167                throw new IllegalArgumentException( "Key cannot be null." );
168            }
169            String key = arg0.toString();
170    
171            // Iterate through each line and replace anything matching our key
172            int idx = 0;
173            while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) )
174            {
175                int prevret = m_propertyString.lastIndexOf( "\n", idx );
176                if ( prevret != -1 )
177                {
178                    // Commented lines are skipped
179                    if ( m_propertyString.charAt( prevret + 1 ) == '#' )
180                    {
181                        idx += key.length();
182                        continue;
183                    }
184                }
185    
186                // If "=" present, delete the entire line
187                int eqsign = m_propertyString.indexOf( "=", idx );
188                if ( eqsign != -1 )
189                {
190                    int ret = m_propertyString.indexOf( "\n", eqsign );
191                    m_propertyString = TextUtil.replaceString( m_propertyString, prevret, ret, "" );
192                    return;
193                }
194            }
195        }
196    
197        private void writeProperty( Object arg0, Object arg1 )
198        {
199            // Get key and value
200            if ( arg0 == null )
201            {
202                throw new IllegalArgumentException( "Key cannot be null." );
203            }
204            if ( arg1 == null )
205            {
206                arg1 = "";
207            }
208            String key = arg0.toString();
209            String value = TextUtil.native2Ascii( arg1.toString() );
210    
211            // Iterate through each line and replace anything matching our key
212            int idx = 0;
213            while( ( idx < m_propertyString.length() ) && ( ( idx = m_propertyString.indexOf( key, idx ) ) != -1 ) )
214            {
215                int prevret = m_propertyString.lastIndexOf( "\n", idx );
216                if ( prevret != -1 )
217                {
218                    // Commented lines are skipped
219                    if ( m_propertyString.charAt( prevret + 1 ) == '#' )
220                    {
221                        idx += key.length();
222                        continue;
223                    }
224                }
225    
226                // If "=" present, replace everything in line after it
227                int eqsign = m_propertyString.indexOf( "=", idx );
228                if ( eqsign != -1 )
229                {
230                    int ret = m_propertyString.indexOf( "\n", eqsign );
231                    if ( ret == -1 )
232                    {
233                        ret = m_propertyString.length();
234                    }
235                    m_propertyString = TextUtil.replaceString( m_propertyString, eqsign + 1, ret, value );
236                    return;
237                }
238            }
239    
240            // If it was not found, we'll add it to the end.
241            m_propertyString += "\n" + key + " = " + value + "\n";
242        }
243    
244    }