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