View Javadoc

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 }