Подтвердить что ты не робот

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

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

Проблема

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

dir = c:\\mydir

Необходим

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

dir = c:\mydir
4b9b3361

Ответ 1

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

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

Ответ 2

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

  • используйте свойства XML вместо
  • Создайте собственный парсер для модифицированного формата .properties без экранов

Ответ 3

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

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 будет выглядеть так, как вам нужно, но вы будете иметь значения свойств, готовые к использованию.

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

Ответ 4

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

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

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

  • преобразуйте формат в канонический, а затем используйте его для загрузки файлов или
  • проанализируйте этот формат и заполните его 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;
    }
}

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

Ответ 5

Основываясь на @Ian Harrigan, вот полное решение для получения файла свойств Netbeans (и другого файла свойств экранирования) прямо из текстовых файлов ascii и в 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

Ответ 6

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

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

Ответ 7

@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);

Ответ 8

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

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

dir = c:/mydir