/* **********************************************************************
/*
* NOTE: This copyright does *not* cover user programs that use Hyperic
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004-2012], VMware, Inc.
* This file is part of Hyperic.
*
* Hyperic is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. This program is distributed
* in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.tools.ant.utils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Merged between two properties files with precedence and filtering whilst keeping the original's comments.
* @author guy
*
*/
public class PropertiesMerger extends Properties{
private static Method saveConvertMethod ;
private final String outputFilePath ;
private Map<String,String[]> delta ;
private boolean isLoaded ;
private String baseFileContent ;
private final PropertiesMergerFilter filter ;
private Collection<String> pruneProperties ;
static {
try{
saveConvertMethod = Properties.class.getDeclaredMethod("saveConvert", String.class, boolean.class, boolean.class) ;
saveConvertMethod.setAccessible(true) ;
}catch(Throwable t) {
throw (t instanceof RuntimeException ? (RuntimeException) t: new RuntimeException(t)) ;
}//EO catch block
}//EO static block
public PropertiesMerger(final String outputFilePath, final PropertiesMergerFilter filter) {
this.outputFilePath = outputFilePath ;
this.filter = filter ;
this.delta = new HashMap<String, String[]>() ;
}//EOM
@Override
public Object put(Object key, Object value) {
Object oPrevious = super.put(key, value);
if(this.isLoaded && !value.equals(oPrevious)) this.delta.put(key.toString(), new String[] { value.toString(), (String) oPrevious}) ;
return oPrevious ;
}//EOM
@Override
public final Object remove(Object key) {
final Object oExisting = super.remove(key);
this.delta.remove(key) ;
return oExisting ;
}//EOM
public final void setBaseFile(final String path) throws IOException{
this.load(path) ;
}//EOM
public final void setPrunePropertiesList(final Collection<String> pruneProperties) {
this.pruneProperties = pruneProperties;
}//EOM
public final void setOverridePropertyValues(final Map<String,String> mapOverrideValues) {
if(mapOverrideValues == null) return ;
String key = null, value = null, previousValue = null ;
for(Map.Entry<String,String> entry : mapOverrideValues.entrySet()) {
key = (String) entry.getKey();
value = (String) entry.getValue() ;
previousValue = (String) this.getProperty(key) ;
if(this.isLoaded && !value.equals(previousValue)) this.delta.put(key.toString(), new String[] { value, previousValue}) ;
}//EO while there are more override property values
}//EOM
public final void setOverrideFile(final String path) throws IOException{
final Properties overrideProperties = new Properties() ;
overrideProperties.load(new FileInputStream(new File(path))) ;
String key = null, value = null ;
for(Map.Entry<Object,Object> entry : overrideProperties.entrySet()) {
key = (String) entry.getKey();
value = (String) entry.getValue() ;
if(filter != null && filter.apply(key, value, this)) {
System.out.println("Overriding property: " + key + " with value: " + value);
this.put(key, value) ;
}//EO if should override
}//EO while there are more properties to iterate over
}//EOM
public void load(final String path) throws IOException {
if(this.isLoaded) throw new IllegalStateException("Load cannot be invoked more than once, use setOverideFile instead") ;
InputStream fis = null, fis1 = null ;
try{
final File file = new File(path) ;
if(!file.exists()) throw new IOException(file + " does not exist or is not readable") ;
//else
fis = new FileInputStream(file) ;
//first read the content into a string
final byte[] arrFileContent = new byte[(int)fis.available()] ;
fis.read(arrFileContent) ;
this.baseFileContent = new String(arrFileContent) ;
fis1 = new ByteArrayInputStream(arrFileContent) ;
this.load(fis1);
}catch(Throwable t) {
throw (t instanceof IOException ? (IOException)t : new IOException(t)) ;
}finally{
if(fis != null) fis.close() ;
if(fis1 != null) fis1.close() ;
}//EO catch block
}//EOM
@Override
public final void load(final InputStream inStream) throws IOException {
try{
super.load(inStream);
}finally{
this.isLoaded = true ;
}//EO catch block
}//EOM
public final void merge() throws IOException {
if(this.delta.isEmpty()) return ;
FileOutputStream fos = null ;
String key = null, value = null, oldValue = null, escapedOldValue = null;
Pattern pattern = null ;
Matcher matcher = null ;
String[] arrValues = null;
try{
final String REGEX_ESCAPE_CHARS_REGEX = "([?()+\\\\])" ;
final String REGEX_ESCAPE_CHARS_REPLACEMENT = "\\\\$1" ;
final String WHITESPACSES_REGEX = "(?<!^)\\s+(?!$)" ;
final String WHITESPACES_REPLACEMENT = "(\\\\s*.*\\\\s*)" ;
for(Map.Entry<String,String[]> entry : this.delta.entrySet()) {
key = (String) saveConvertMethod.invoke(this, entry.getKey(), true/*escapeSpace*/, true /*escUnicode*/);
arrValues = entry.getValue() ;
value = (String) saveConvertMethod.invoke(this, arrValues[0], false/*escapeSpace*/, true /*escUnicode*/);
//if the arrValues[1] == null then this is a new property
if(arrValues[1] == null) {
this.baseFileContent = this.baseFileContent + "\n" + key + "=" + value ;
}else {
//first escape all characters with special meaning then replace all whitespaces which do not appear
//at the beginning or end of the input with multi line whilespace regex
//additionally add a replacement alternative if the tuple whereby the properties file reserved chars are escaped
//(viable scenario with auto generated property files)
//eventually the replacement regex will follow the format of: <key= value|escaped value>
escapedOldValue = (String) saveConvertMethod.invoke(this, arrValues[1], false/*escapeSpace*/, true /*escUnicode*/);
oldValue = arrValues[1].replaceAll(REGEX_ESCAPE_CHARS_REGEX, REGEX_ESCAPE_CHARS_REPLACEMENT).replaceAll(WHITESPACSES_REGEX, WHITESPACES_REPLACEMENT) ;
escapedOldValue = escapedOldValue.replaceAll(REGEX_ESCAPE_CHARS_REGEX, REGEX_ESCAPE_CHARS_REPLACEMENT).replaceAll(WHITESPACSES_REGEX, WHITESPACES_REPLACEMENT) ;
if(!oldValue.equals(escapedOldValue)) {
oldValue = "(?:" + oldValue + "|" + escapedOldValue + ")" ;
}//EO if escape value has different representation
pattern = Pattern.compile(key+"\\s*=(\\s*.*\\s*)"+ oldValue , Pattern.MULTILINE) ;
//pattern = Pattern.compile(key+"\\s*=.*\n", Pattern.MULTILINE) ;
matcher = pattern.matcher(this.baseFileContent) ;
this.baseFileContent = matcher.replaceAll(key + "=" + value) ;
}//EO else if existing property
System.out.println("Adding/Replacing " + key + "-->" + arrValues[1] + " with: " + value) ;
}//EO while there are more entries ;
this.pruneProperties() ;
final File outputFile = new File(this.outputFilePath) ;
outputFile.delete() ;
fos = new FileOutputStream(outputFile) ;
fos.write(this.baseFileContent.getBytes()) ;
}catch(Throwable t) {
throw (t instanceof IOException ? (IOException)t : new IOException(t)) ;
}finally{
if(fos != null) {
fos.flush() ;
fos.close() ;
}//EO if bw was initialized
}//EO catch block
}//EOM
private final void pruneProperties() throws Throwable {
if(this.pruneProperties == null) return ;
String value = null ;
Matcher matcher = null ;
Pattern pattern = null ;
for(String property : this.pruneProperties) {
if(this.containsKey(property)) {
value = (String) this.get(property) ;
value = (value == null ? "" : value.replaceAll("([():+])", "\\\\$1").replaceAll("\\s+", "(\\\\s*.*\\\\s*)")) ;
pattern = Pattern.compile(property+"\\s*=(\\s*.*\\s*)"+ value, Pattern.MULTILINE) ;
//pattern = Pattern.compile(key+"\\s*=.*\n", Pattern.MULTILINE) ;
matcher = pattern.matcher(this.baseFileContent) ;
this.baseFileContent = matcher.replaceAll("") ;
}//EO if property exists
}//EO while there are more properties
}//EOM
public static interface PropertiesMergerFilter {
boolean apply(String propertyName, String propertyValue, final Properties properties) ;
}//EO inner interface PropertiesMergerFilter
public static void main(String[] args) throws Throwable {
//String regex = "server.database-password=ENC(ZsbvmndZgX3mclWtDCjX7g==) 2ndline" ;
final String input = "#server.database-url=jdbc\\:mysql\\://10.131.9.171\\:3306/HQ\n" +
"#server.encryption-key=password\n" +
"#server.database-user=hqadmin\n" +
"#server.database-password=ENC(ZH4ttiFMPh4tc4kR+F/wFA\\=\\=)\n" +
"server.database-url=jdbc:postgresql://127.0.0.1:9432/hqdb?protocolVersion=2\n" +
"server.encryption-key=mickymouse\n" +
"server.database-user=firstcit\n" +
"server.database-password=ENC(JN0nmlpNxvjZYotfEgew9MOXG2CXm8wz)\n" +
"\n" +
"CAM_SERVER_VERSION=4.6.0.1\n" +
"CAM_SCHEMA_VERSION=3.210" ;
final String regex = "server.database-url\\s*=(\\s*.*\\s*)(?:jdbc:postgresql://127.0.0.1:9432/hqdb?protocolVersion=2|jdbc\\:postgresql\\://127.0.0.1\\:9432/hqdb\\?protocolVersion\\=2)" ;
final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE) ;
final Matcher matcher = pattern.matcher(input) ;
System.out.println(matcher.replaceAll("server.database-url=this.is.the.url")) ;
/* String regex1 = "server.database-url=jdbc\\:mysql\\://10.131.9.171\\:3306/FC" ;
String regex2 = "server.database-url=jdbc:mysql://10.131.9.171:3306/FC" ;
regex = regex.replaceAll("([()+\\\\])", "\\\\$1").replaceAll("(?<!^)\\s+(?!$)", "(\\\\s*.*\\\\s*)") ;
regex = regex + "|" + regex1.replaceAll("([()+\\\\])", "\\\\$1").replaceAll("(?<!^)\\s+(?!$)", "(\\\\s*.*\\\\s*)") ;
System.out.println(regex);
System.out.println();
final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE) ;
final Matcher matcher = pattern.matcher(input) ;
System.out.println(matcher.replaceAll("server.database-url=this.is.the.url")) ; */
}//EOM
}//EOC