package org.codehaus.mojo.appassembler.util; /* * The MIT License * * Copyright 2005-2008 The Codehaus. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import org.codehaus.plexus.util.IOUtil; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A class to read/write a properties file, and retain the formatting through modifications. */ public class FormattedProperties { private static final Pattern LIST_KEY_PATTERN = Pattern.compile( "^(.*)\\.[0-9]+$" ); /** * The properties delegate. */ private final Properties properties = new Properties(); /** * The last line where a given property was encountered. */ private Map propertyLines; /** * The actual lines of the file for writing back as it was. */ private List fileLines; /** * Keeping track of properties that are lists. */ private Map listProperties = new HashMap(); /** * A map of property chains to add properties after. */ private Map afterProperties = new HashMap(); public void setProperty( String key, String value ) { synchronized ( properties ) { properties.setProperty( key, value ); // does the property look like a list (ends in .X where X is an integer)? Matcher m = LIST_KEY_PATTERN.matcher( key ); if ( m.matches() ) { String listKey = m.group( 1 ); // add the property to a list keyed by the base key of the list List p = (List) listProperties.get( listKey ); if ( p == null ) { p = new ArrayList(); listProperties.put( listKey, p ); } p.add( key ); } } } public String getProperty( String key ) { synchronized ( properties ) { return properties.getProperty( key ); } } public String getProperty( String key, String defaultValue ) { synchronized ( properties ) { return properties.getProperty( key, defaultValue ); } } public void removeProperty( String key ) { synchronized ( properties ) { properties.remove( key ); } } /** * Read in the properties from the given stream. Note that this will be used as the basis of the next formatted * write, even though properties from any previous read are still retained. This allows adding properties to the top * of the file. * * @param inputStream the stream to read from * @throws IOException if there is a problem reading the stream */ public void read( InputStream inputStream ) throws IOException { synchronized ( properties ) { fileLines = new ArrayList(); propertyLines = new HashMap(); BufferedReader r = new BufferedReader( new InputStreamReader( inputStream ) ); try { int lineNo = 1; String line = r.readLine(); while ( line != null ) { // parse the key and value. No = means it's not a property, multiple = will be attributed to the // value String[] pair = line.split( "=", 2 ); String key = pair[0]; String value; if ( pair.length > 1 ) { value = pair[1].trim(); // is the line a comment? boolean commented = false; if ( key.startsWith( "#" ) ) { commented = true; key = key.substring( 1 ); } key = key.trim(); // if it's not commented, set the property if ( !commented ) { // must use our setProperty to update list properties too setProperty( key, value ); } // regardless of whether it's a comment, track the key it might have been (only the base key if // it's a list) so we know where to add any new properties later Matcher m = LIST_KEY_PATTERN.matcher( key ); if ( m.matches() ) { key = m.group( 1 ); } propertyLines.put( key, new Integer( lineNo ) ); } fileLines.add( line ); line = r.readLine(); lineNo++; } } finally { IOUtil.close( r ); } } } public void save( OutputStream outputStream ) { synchronized ( properties ) { PrintWriter writer = new PrintWriter( new OutputStreamWriter( outputStream ) ); // TODO: we should be updating the fileLines and propertyLines as a result of this too try { Set writtenProperties = new HashSet(); // tracking the old file lines, we'll just add ours in as we go for ( int i = 0; i < fileLines.size(); i++ ) { String line = (String) fileLines.get( i ); // skip processing empty lines (though they are written later) if ( line.trim().length() > 0 ) { String[] pair = line.split( "=", 2 ); String key = pair[0]; if ( key.startsWith( "#" ) ) { // comments are written back out verbatim. If we match the key, we'll write the value below // it (unless there is a later instance in a list) key = key.substring( 1 ); writer.println( line ); } key = key.trim(); // look for an exact match on the key to replace on the current line if ( new Integer( i + 1 ).equals( propertyLines.get( key ) ) ) { String value = properties.getProperty( key ); if ( value != null ) { writer.println( key + "=" + value ); writtenProperties.add( key ); } } // look for chained properties to add if ( afterProperties.containsKey( key ) ) { List p = (List) afterProperties.get( key ); for ( Iterator j = p.iterator(); j.hasNext(); ) { String pKey = (String) j.next(); String value = properties.getProperty( pKey ); if ( value != null && !writtenProperties.contains( pKey ) ) { writer.println( pKey + "=" + value ); writtenProperties.add( pKey ); } } } // check if we matched the last key in a list, and if so write all unwritten list properties Matcher m = LIST_KEY_PATTERN.matcher( key ); if ( m.matches() ) { key = m.group( 1 ); if ( new Integer( i + 1 ).equals( propertyLines.get( key ) ) ) { List p = (List) listProperties.get( key ); if ( p != null ) { for ( Iterator j = p.iterator(); j.hasNext(); ) { String itemKey = (String) j.next(); if ( !writtenProperties.contains( itemKey ) ) { String value = properties.getProperty( itemKey ); if ( value != null ) { writer.println( itemKey + "=" + value ); writtenProperties.add( itemKey ); } } } } } } } else { writer.println( line ); } } for ( Iterator i = properties.keySet().iterator(); i.hasNext(); ) { String key = (String) i.next(); if ( !writtenProperties.contains( key ) ) { String value = properties.getProperty( key ); if ( value != null ) { writer.println( key + "=" + value ); } } } } finally { IOUtil.close( writer ); } } } public void setPropertyAfter( String key, String value, String afterProperty ) { List p = (List) afterProperties.get( afterProperty ); if ( p == null ) { p = new ArrayList(); afterProperties.put( afterProperty, p ); } p.add( key ); setProperty( key, value ); } }