1 /**
2 * The MIT License
3 *
4 * Copyright 2006-2012 The Codehaus.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy of
7 * this software and associated documentation files (the "Software"), to deal in
8 * the Software without restriction, including without limitation the rights to
9 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10 * of the Software, and to permit persons to whom the Software is furnished to do
11 * so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24 package org.codehaus.mojo.appassembler.util;
25
26 import org.codehaus.plexus.util.IOUtil;
27
28 import java.io.BufferedReader;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.InputStreamReader;
32 import java.io.OutputStream;
33 import java.io.OutputStreamWriter;
34 import java.io.PrintWriter;
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Properties;
42 import java.util.Set;
43 import java.util.regex.Matcher;
44 import java.util.regex.Pattern;
45
46 /**
47 * A class to read/write a properties file, and retain the formatting through modifications.
48 */
49 public class FormattedProperties
50 {
51 private static final Pattern LIST_KEY_PATTERN = Pattern.compile ( "^(.*)\\.[0-9]+$" );
52
53 /**
54 * The properties delegate.
55 */
56 private final Properties properties = new Properties ( );
57
58 /**
59 * The last line where a given property was encountered.
60 */
61 private Map propertyLines;
62
63 /**
64 * The actual lines of the file for writing back as it was.
65 */
66 private List fileLines;
67
68 /**
69 * Keeping track of properties that are lists.
70 */
71 private Map listProperties = new HashMap ( );
72
73 /**
74 * A map of property chains to add properties after.
75 */
76 private Map afterProperties = new HashMap ( );
77
78 public void setProperty ( String key, String value )
79 {
80 synchronized ( properties )
81 {
82 properties.setProperty ( key, value );
83
84 // does the property look like a list (ends in .X where X is an integer)?
85 Matcher m = LIST_KEY_PATTERN.matcher ( key );
86 if ( m.matches ( ) )
87 {
88 String listKey = m.group ( 1 );
89
90 // add the property to a list keyed by the base key of the list
91 List p = ( List ) listProperties.get ( listKey );
92 if ( p == null )
93 {
94 p = new ArrayList ( );
95 listProperties.put ( listKey, p );
96 }
97 p.add ( key );
98 }
99 }
100 }
101
102 public String getProperty ( String key )
103 {
104 synchronized ( properties )
105 {
106 return properties.getProperty ( key );
107 }
108 }
109
110 public String getProperty ( String key, String defaultValue )
111 {
112 synchronized ( properties )
113 {
114 return properties.getProperty ( key, defaultValue );
115 }
116 }
117
118 public void removeProperty ( String key )
119 {
120 synchronized ( properties )
121 {
122 properties.remove ( key );
123 }
124 }
125
126 /**
127 * Read in the properties from the given stream. Note that this will be used as the basis of the next formatted
128 * write, even though properties from any previous read are still retained. This allows adding properties to the top
129 * of the file.
130 *
131 * @param inputStream
132 * the stream to read from
133 * @throws IOException
134 * if there is a problem reading the stream
135 */
136 public void read ( InputStream inputStream )
137 throws IOException
138 {
139 synchronized ( properties )
140 {
141 fileLines = new ArrayList ( );
142 propertyLines = new HashMap ( );
143
144 BufferedReader r = new BufferedReader ( new InputStreamReader ( inputStream ) );
145
146 try
147 {
148 int lineNo = 1;
149 String line = r.readLine ( );
150 while ( line != null )
151 {
152 // parse the key and value. No = means it's not a property, multiple = will be attributed to the
153 // value
154 String[] pair = line.split ( "=", 2 );
155 String key = pair[0];
156 String value;
157 if ( pair.length > 1 )
158 {
159 value = pair[1].trim ( );
160
161 // is the line a comment?
162 boolean commented = false;
163 if ( key.startsWith ( "#" ) )
164 {
165 commented = true;
166 key = key.substring ( 1 );
167 }
168
169 key = key.trim ( );
170
171 // if it's not commented, set the property
172 if ( !commented )
173 {
174 // must use our setProperty to update list properties too
175 setProperty ( key, value );
176 }
177
178 // regardless of whether it's a comment, track the key it might have been (only the base key if
179 // it's a list) so we know where to add any new properties later
180 Matcher m = LIST_KEY_PATTERN.matcher ( key );
181 if ( m.matches ( ) )
182 {
183 key = m.group ( 1 );
184 }
185
186 propertyLines.put ( key, new Integer ( lineNo ) );
187 }
188
189 fileLines.add ( line );
190
191 line = r.readLine ( );
192 lineNo++;
193 }
194 }
195 finally
196 {
197 IOUtil.close ( r );
198 }
199 }
200 }
201
202 public void save ( OutputStream outputStream )
203 {
204 synchronized ( properties )
205 {
206 PrintWriter writer = new PrintWriter ( new OutputStreamWriter ( outputStream ) );
207
208 // TODO: we should be updating the fileLines and propertyLines as a result of this too
209 try
210 {
211 Set writtenProperties = new HashSet ( );
212
213 // tracking the old file lines, we'll just add ours in as we go
214 for ( int i = 0; i < fileLines.size ( ); i++ )
215 {
216 String line = ( String ) fileLines.get ( i );
217
218 // skip processing empty lines (though they are written later)
219 if ( line.trim ( ).length ( ) > 0 )
220 {
221 String[] pair = line.split ( "=", 2 );
222 String key = pair[0];
223 if ( key.startsWith ( "#" ) )
224 {
225 // comments are written back out verbatim. If we match the key, we'll write the value below
226 // it (unless there is a later instance in a list)
227 key = key.substring ( 1 );
228 writer.println ( line );
229 }
230
231 key = key.trim ( );
232
233 // look for an exact match on the key to replace on the current line
234 if ( new Integer ( i + 1 ).equals ( propertyLines.get ( key ) ) )
235 {
236 String value = properties.getProperty ( key );
237 if ( value != null )
238 {
239 writer.println ( key + "=" + value );
240 writtenProperties.add ( key );
241 }
242 }
243
244 // look for chained properties to add
245 if ( afterProperties.containsKey ( key ) )
246 {
247 List p = ( List ) afterProperties.get ( key );
248 for ( Iterator j = p.iterator ( ); j.hasNext ( ); )
249 {
250 String pKey = ( String ) j.next ( );
251
252 String value = properties.getProperty ( pKey );
253 if ( value != null && !writtenProperties.contains ( pKey ) )
254 {
255 writer.println ( pKey + "=" + value );
256 writtenProperties.add ( pKey );
257 }
258 }
259 }
260
261 // check if we matched the last key in a list, and if so write all unwritten list properties
262 Matcher m = LIST_KEY_PATTERN.matcher ( key );
263 if ( m.matches ( ) )
264 {
265 key = m.group ( 1 );
266
267 if ( new Integer ( i + 1 ).equals ( propertyLines.get ( key ) ) )
268 {
269 List p = ( List ) listProperties.get ( key );
270 if ( p != null )
271 {
272 for ( Iterator j = p.iterator ( ); j.hasNext ( ); )
273 {
274 String itemKey = ( String ) j.next ( );
275
276 if ( !writtenProperties.contains ( itemKey ) )
277 {
278 String value = properties.getProperty ( itemKey );
279 if ( value != null )
280 {
281 writer.println ( itemKey + "=" + value );
282 writtenProperties.add ( itemKey );
283 }
284 }
285 }
286 }
287 }
288 }
289 }
290 else
291 {
292 writer.println ( line );
293 }
294 }
295
296 for ( Iterator i = properties.keySet ( ).iterator ( ); i.hasNext ( ); )
297 {
298 String key = ( String ) i.next ( );
299 if ( !writtenProperties.contains ( key ) )
300 {
301 String value = properties.getProperty ( key );
302 if ( value != null )
303 {
304 writer.println ( key + "=" + value );
305 }
306 }
307 }
308 }
309 finally
310 {
311 IOUtil.close ( writer );
312 }
313 }
314 }
315
316 public void setPropertyAfter ( String key, String value, String afterProperty )
317 {
318 List p = ( List ) afterProperties.get ( afterProperty );
319 if ( p == null )
320 {
321 p = new ArrayList ( );
322 afterProperties.put ( afterProperty, p );
323 }
324 p.add ( key );
325
326 setProperty ( key, value );
327 }
328 }