Чтение файла свойств Java без экранирования значений - PullRequest
19 голосов
/ 04 июня 2011

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

Проблема

В файлах свойств необходимо экранировать значения, например,

dir = c:\\mydir

Требуется

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

dir = c:\mydir

Ответы [ 8 ]

19 голосов
/ 12 июня 2011

Почему бы просто не расширить класс свойств, чтобы включить удаление двойных косых черт. Хорошей особенностью этого будет то, что в остальной части вашей программы вы все еще можете использовать оригинальный класс 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);

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

6 голосов
/ 09 июня 2011

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

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());
}

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

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

6 голосов
/ 04 июня 2011

Два варианта:

  • вместо
  • Создайте собственный синтаксический анализатор для измененного формата .properties без экранирования
3 голосов
/ 23 ноября 2011

Основываясь на @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  : /5556795/chtenie-faila-svoistv-java-bez-ekranirovaniya-znachenii.
 * 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
3 голосов
/ 12 июня 2011

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

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

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

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

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


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

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

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

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

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

В вашем примере dir = c:\mydir ключ будетdir и значение c:\mydir.

Если мы хотим, чтобы наши ключи и значения содержали любой символ Unicode (кроме упомянутых запрещенных), мы должны использовать 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" >

Every \ встречающийся в ключе или значении теперь является реальным обратным слешем, а не чем-то, что ускользает от чего-то еще.Таким образом, чтобы преобразовать его в исходный формат, нам просто нужно удвоить его, как предложил Грекз, например, в считывающем устройстве для фильтрации:

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;
    }
}

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

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

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() после этого, мы можем экспортировать его в исходный формат.

2 голосов
/ 11 июня 2011

@ 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);
2 голосов
/ 04 июня 2011

Вы можете попробовать использовать Splitter в guava: разделить на '=' и построить карту из полученного Iterable.

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

0 голосов
/ 06 июля 2011

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

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

dir = c:/mydir
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...