Чтение файла свойств Java без экранирования значений

мое приложение должно использовать a .файл свойств для конфигурации. В файлах свойств пользователям разрешено указывать пути.

8 ответов


почему бы просто не расширить класс свойств, чтобы включить зачистку двойных косых черт. Хорошей особенностью этого будет то, что через остальную часть вашей программы вы все еще можете использовать оригинал Properties класса.

public class PropertiesEx extends Properties {
    public void load(FileInputStream fis) throws IOException {
        Scanner in = new Scanner(fis);
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        while(in.hasNext()) {
            out.write(in.nextLine().replace("\","\\").getBytes());
            out.write("\n".getBytes());
        }

        InputStream is = new ByteArrayInputStream(out.toByteArray());
        super.load(is);
    }
}

использование нового класса просто как:

PropertiesEx p = new PropertiesEx();
p.load(new FileInputStream("C:\temp\demo.properties"));
p.list(System.out);

код зачистки также может быть улучшен, но общий принцип существует.


два варианта:

  • использовать свойства XML
  • Writer свой собственный парсер для измененного .формат свойств без escapes

вы можете "предварительно обработать" файл перед загрузкой свойств, например:

public InputStream preprocessPropertiesFile(String myFile) throws IOException{
    Scanner in = new Scanner(new FileReader(myFile));
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    while(in.hasNext())
        out.write(in.nextLine().replace("\","\\").getBytes());
    return new ByteArrayInputStream(out.toByteArray());
}

и ваш код может выглядеть следующим образом

Properties properties = new Properties();
properties.load(preprocessPropertiesFile("path/myfile.properties"));

этого, ваш .файл свойств будет выглядеть так, как вам нужно, но у вас будут готовые значения свойств.

*Я знаю, что должны быть лучшие способы манипулировать файлами, но я надеюсь, что это поможет.


правильным способом было бы предоставить вашим пользователям редактор файлов свойств (или плагин для их любимого текстового редактора), который позволяет им вводить текст как чистый текст и сохранять файл в формате файла свойств.

если вы этого не хотите, вы фактически определяете новый формат для той же (или подмножества) модели контента, что и файлы свойств.

пройти весь путь и на самом деле указать ваш формат, а затем подумайте о способе либо

  • преобразуйте формат в канонический, а затем используйте его для загрузки файлов или
  • проанализируйте этот формат и заполните Properties объект от него.

оба этих подхода будут работать только напрямую, если вы действительно можете контролировать создание объекта свойства, иначе вам придется хранить преобразованный формат с вашим приложением.


Итак, давайте посмотрим, как мы можем определить это. Модель контента обычные файлы собственность просто:

  • карта строковых ключей к строковым значениям, позволяющая произвольные строки Java.

экранирование, которого вы хотите избежать, служит только для разрешения произвольных строк Java, а не только их подмножества.

часто достаточным подмножеством было бы:

  • карта строковых ключей (не содержит пробелов,: или =) для строковых значений (не содержит никаких пробелов или разрывов линий).
dir = c:\mydir, ключ будет dir и значение c:\mydir.

если мы хотим, чтобы наши ключи и значения содержали любой символ Юникода (кроме упомянутых запрещенных), мы должны использовать UTF-8 (или UTF-16) в качестве кодировки хранения - поскольку у нас нет способа избежать символов вне кодировки хранения. В противном случае US-ASCII или ISO-8859-1 (как обычные файлы свойств) или любой другой кодировки, поддерживаемой Java было бы достаточно, но не забудьте включить это в спецификацию модели контента (и не забудьте прочитать его таким образом).

поскольку мы ограничили нашу модель контента, чтобы все "опасные" символы не мешали, теперь мы можем определить формат файла просто так:

<simplepropertyfile> ::= (<line> <line break> )*
<line>               ::= <comment> | <empty> | <key-value>
<comment>            ::= <space>* "#" < any text excluding line breaks >
<key-value>          ::= <space>* <key> <space>* "=" <space>* <value> <space>*
<empty>              ::= <space>*
<key>                ::= < any text excluding ':', '=' and whitespace >
<value>              ::= < any text starting and ending not with whitespace,
                           not including line breaks >
<space>              ::= < any whitespace, but not a line break >
<line break>         ::= < one of "\n", "\r", and "\r\n" >
\ происходящее в любом ключе или значении теперь является реальные обратная косая черта, а не что-то, что ускользает от чего-то другого. Таким образом, для преобразуя его в исходный формат, нам просто нужно удвоить его, как предложил Грекз, например, в читателе фильтрации:
public DoubleBackslashFilter extends FilterReader {
    private boolean bufferedBackslash = false;

    public DoubleBackslashFilter(Reader org) {
        super(org);
    }

    public int read() {
        if(bufferedBackslash) {
            bufferedBackslash = false;
            return '\';
        }
        int c = super.read();
        if(c == '\')
           bufferedBackslash = true;
        return c;
    }

    public int read(char[] buf, int off, int len) {
        int read = 0;
        if(bufferedBackslash) {
           buf[off] = '\';
           read++;
           off++;
           len --;
           bufferedBackslash = false;
        }
        if(len > 1) {
           int step = super.read(buf, off, len/2);
           for(int i = 0; i < step; i++) {
               if(buf[off+i] == '\') {
                  // shift everything from here one one char to the right.
                  System.arraycopy(buf, i, buf, i+1, step - i);
                  // adjust parameters
                  step++; i++;
               }
           }
           read += step;
        }
        return read;
    }
}

затем мы передадим этот считыватель нашему объекту свойств (или сохраним содержимое в новый файл).

вместо этого мы могли бы просто проанализировать этот формат сами.

public Properties parse(Reader in) {
    BufferedReader r = new BufferedReader(in);
    Properties prop = new Properties();
    Pattern keyValPattern = Pattern.compile("\s*=\s*");
    String line;
    while((line = r.readLine()) != null) {
        line = line.trim(); // remove leading and trailing space
        if(line.equals("") || line.startsWith("#")) {
            continue; // ignore empty and comment lines
        }
        String[] kv = line.split(keyValPattern, 2);
        // the pattern also grabs space around the separator.
        if(kv.length < 2) {
            // no key-value separator. TODO: Throw exception or simply ignore this line?
            continue;
        }
        prop.setProperty(kv[0], kv[1]);
    }
    r.close();
    return prop;
}

опять же, с помощью Properties.store() после этого, мы можем экспортировать его в оригинальном формате.


на основе @Ian Harrigan, вот полное решение для получения файла свойств Netbeans (и другого файла экранирующих свойств) прямо из и в текстовые файлы ascii :

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

/**
 * This class allows to handle Netbeans properties file. 
 * It is based on the work of  : http://stackoverflow.com/questions/6233532/reading-java-properties-file-without-escaping-values.
 * It overrides both load methods in order to load a netbeans property file, taking into account the \ that 
 * were escaped by java properties original load methods.
 * @author stephane
 */
public class NetbeansProperties extends Properties {
    @Override
    public synchronized void load(Reader reader) throws IOException {
        BufferedReader bfr = new BufferedReader( reader );
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        String readLine = null;
        while( (readLine = bfr.readLine()) != null ) {
            out.write(readLine.replace("\","\\").getBytes());
            out.write("\n".getBytes());
        }//while

        InputStream is = new ByteArrayInputStream(out.toByteArray());
        super.load(is);
    }//met

    @Override
    public void load(InputStream is) throws IOException {
        load( new InputStreamReader( is ) );
    }//met

    @Override
    public void store(Writer writer, String comments) throws IOException {
        PrintWriter out = new PrintWriter( writer );
        if( comments != null ) {
            out.print( '#' );
            out.println( comments );
        }//if
        List<String> listOrderedKey = new ArrayList<String>();
        listOrderedKey.addAll( this.stringPropertyNames() );
        Collections.sort(listOrderedKey );
        for( String key : listOrderedKey ) {
            String newValue = this.getProperty(key);
            out.println( key+"="+newValue  );
       }//for
    }//met

    @Override
    public void store(OutputStream out, String comments) throws IOException {
        store( new OutputStreamWriter(out), comments );
    }//met
}//class

вы можете попробовать использовать гуавы в Splitter: split on '=' и построить карту из результирующего Iterable.

недостатком этого решения является то, что оно не поддерживает комментарии.


@pdeva: еще одно решение

//Reads entire file in a String 
//available in java1.5
Scanner scan = new Scanner(new File("C:/workspace/Test/src/myfile.properties"));   
scan.useDelimiter("\Z");   
String content = scan.next();

//Use apache StringEscapeUtils.escapeJava() method to escape java characters
ByteArrayInputStream bi=new ByteArrayInputStream(StringEscapeUtils.escapeJava(content).getBytes());

//load properties file
Properties properties = new Properties(); 
properties.load(bi);

это не точный ответ на ваш вопрос, а другое решение, которое может соответствовать вашим потребностям. В Java, вы можете использовать / как разделитель пути, и он будет работать как на Windows, Linux, так и на OSX. Это особенно полезно для относительных путей.

в вашем примере вы можете использовать:

dir = c:/mydir