1

So far, I have this project where I read in a properties file using PropertiesConfiguration (from Apache), edit the values I would like to edit, and then save change to the file. It keeps the comments and formatting and such, but one thing it does change is taking the multi-line values formatted like this:

key=value1,\
    value2,\
    value3

and turns it into the array style:

key=value1,value2,value3

I would like to be able to print those lines formatted as the were before.
I did this via this method:

PropertiesConfiguration config = new PropertiesConfiguration(configFile);
config.setProperty(key,value);
config.save();
Luke Gaskell
  • 175
  • 4
  • 15

2 Answers2

0

I created a work around in case anyone else needs this functionality. Also, there is probably a better way to do this, but this solution currently works for me.

First, set your PropertiesConfiguration delimiter to the new line character like so:

PropertiesConfiguration config = new PropertiesConfiguration(configFile);
config.setListDelimiter('\n');

Then you will need to iterate through and update all properties (to set the format):

Iterator<String> keys = config.getKeys();
    while (keys.hasNext()) {
        String key = keys.next();

        config.setProperty(key,setPropertyFormatter(key, config.getProperty(key))) ;

    }

use this method to format your value list data (as shown above):

private List<String> setPropertyFormatter(String key, Object list) {
    List<String> tempProperties = new ArrayList<>();
    Iterator<?> propertyIterator = PropertyConverter.toIterator(list, '\n');;
    String indent = new String(new char[key.length() + 1]).replace('\0', ' ');

    Boolean firstIteration = true;
    while (propertyIterator.hasNext()) {
        String value = propertyIterator.next().toString();

        Boolean lastIteration = !propertyIterator.hasNext();

        if(firstIteration && lastIteration) {
            tempProperties.add(value);
            continue;
        }

        if(firstIteration) {
            tempProperties.add(value + ",\\");
            firstIteration = false;
            continue;
        }

        if (lastIteration) { 
            tempProperties.add(indent + value);
            continue;
        } 

        tempProperties.add(indent + value + ",\\");
    }



    return tempProperties;
}

Then it is going to be almost correct, except the save function takes the double backslash that is stored in the List, and turns it into 4 back slashes in the file! So you need to replace those with a single backslash. I did this like so:

try {
        config.save(new File(filePath));


        byte[] readIn = Files.readAllBytes(Paths.get(filePath));
        String replacer = new String(readIn, StandardCharsets.UTF_8).replace("\\\\\\\\", "\\");

        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath, false), "UTF-8"));
        bw.write(replacer);
        bw.close();

} catch (ConfigurationException | IOException e) {
        e.printStackTrace();
}
Luke Gaskell
  • 175
  • 4
  • 15
0

With commons-configuration2, you would handle such cases with a custom PropertiesWriter implementation, as described in its documentation under "Custom properties readers and writers" (Reader biased though).

A writer provides a way to govern writing of each character that is to be written to the properties file, so you can achieve pretty much anything you desire with it (via PropertiesWriter.write(String)). There is also a convenient method that writes proper newlines (PropertiesWriter.writeln(String)).

For example, I had to handle classpath entries in a Netbeans Ant project project.properties file:

public class ClasspathPropertiesWriter extends PropertiesConfiguration.PropertiesWriter {
    
    public ClasspathPropertiesWriter(Writer writer, ListDelimiterHandler delimiter) {
        super(writer, delimiter);
    }

    @Override
    public void writeProperty(String key, Object value, boolean forceSingleLine) throws IOException {
        switch (key) {
            case "javac.classpath":
            case "run.classpath":
            case "javac.test.classpath":
            case "run.test.classpath":
                String str = (String) value;
                String[] split = str.split(":");
                if (split.length > 1) {
                    write(key);
                    write("=\\");
                    writeln(null);
                    for (int i = 0; i < split.length; i++) {
                        write("    ");
                        write(split[i]);
                        if (i != split.length - 1) {
                            write(":\\");
                        }
                        writeln(null);
                    }
                    
                } else {
                    super.writeProperty(key, value, forceSingleLine);
                }                
                break;

            default:
                super.writeProperty(key, value, forceSingleLine);
                break;
        }
        
    }
    
}
public class CustomIOFactory extends PropertiesConfiguration.DefaultIOFactory {

    @Override
    public PropertiesConfiguration.PropertiesWriter createPropertiesWriter(
            Writer out, ListDelimiterHandler handler) {
        return new ClasspathPropertiesWriter(out, handler);
    }
    
}
Parameters params = new Parameters();
FileBasedConfigurationBuilder<Configuration> builder =
    new FileBasedConfigurationBuilder<Configuration>(PropertiesConfiguration.class)
    .configure(params.properties()
        .setFileName("project.properties")
        .setIOFactory(new CustomIOFactory());
Configuration config = builder.getConfiguration();
builder.save();
predi
  • 5,528
  • 32
  • 60
  • Note that the code above is just a quick example. Production code should handle this in a more robust way. – predi Jun 02 '21 at 06:47