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 }