2011-06-03 9 views
18

Mi aplicación necesita utilizar un archivo .properties para la configuración. En los archivos de propiedades, los usuarios permiten especificar rutas.Lectura del archivo de propiedades de Java sin valores de escape

Problema

Propiedades archivos necesitan valores que se escaparon, por ejemplo

dir = c:\\mydir 

necesario

Necesito un poco de forma de aceptar un archivo de propiedades donde los valores no se escaparon, para que los usuarios puedan especificar:

dir = c:\mydir 

Respuesta

18

¿Por qué no extender simplemente la clase de propiedades para incorporar la eliminación de barras diagonales dobles? Una buena característica de esto será que a través del resto de su programa todavía puede usar la clase original 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); 
    } 
} 

Utilizando la nueva clase es un simple como:

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

El código de separación también podría mejorarse, pero el principio general es allí.

+0

Gracias por esta pista. Creé una clase de utilidad estática para cargar y guardar archivos de propiedades, pero tu camino es mucho más limpio. Por cierto, eso es lo que se necesita para cargar un poco del archivo de propiedades de netbeans (como project.properties), especialmente cuando se usa el paquete de movilidad. – Snicolas

6

dos opciones:

  • utilizar el formato XML properties lugar
  • escritor su propio programa de análisis para un formato .properties modificado sin escapes
+1

no quiero usar propiedades xml. – pdeva

2

Usted podría tratar de usar la guayaba Splitter: división en '=' y construir un mapa del resultado Iterable.

La desventaja de esta solución es que no admite comentarios.

+2

También: estrictamente hablando, un archivo '.properties' con valores sin escape no está en el formato especificado para los archivos' .properties'. El formato es más complejo de lo que mucha gente piensa (por ejemplo, se especifica que siempre estará en la codificación ISO-8859-1 y es compatible con '\ u' escapes unicode). –

6

Puede "preproceso" el archivo antes de cargar las propiedades, por ejemplo:

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

Y su código podría verse de esta manera

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

Al hacer esto, el archivo .properties se vería lo necesita, pero tendrá los valores de propiedades listos para usar.

* Sé que debería haber mejores formas de manipular archivos, pero espero que esto ayude.

2

@pdeva: una solución más

//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); 
+0

Si está utilizando 'new Scanner (...)' y '.getBytes()', asegúrese de que estén usando el juego de caracteres derecho aquí (ISO-8859-1, ya que esto es lo que usa properties.load). Pero mejor permita que los datos permanezcan en forma de caracteres en lugar de convertirlos a bytes y viceversa, es decir, use StringReader en lugar de ByteArrayInputStream. –

3

La forma correcta sería la de proporcionar a los usuarios un editor de archivos de propiedad (o un plug-in para su editor de texto favorito) que les permite entrar en el texto como texto puro y guardará el archivo en el formato de archivo de propiedad.

Si no lo desea, está definiendo efectivamente un nuevo formato para el mismo (o un subconjunto del) modelo de contenido como los archivos de propiedades.

llegar hasta el final y, de hecho especifique su formato, y luego pensar en una manera de cualquiera

  • transformar el formato de la canónica, y luego usar esto para cargar los archivos, o
  • analiza este formato y rellena un objeto Properties.

Ambos enfoques solo funcionarán directamente si realmente puede controlar la creación de su objeto de propiedad, de lo contrario tendrá que almacenar el formato transformado con su aplicación.


Entonces, veamos cómo podemos definir esto. El modelo de contenido de archivos de propiedades normales es simple:

  • Un mapa de claves de cadena de valores de cadena, ambas cadenas Java que permite arbitrarias.

El escape que desea evitar sirve solo para permitir cadenas de Java arbitrarias, y no solo un subconjunto de éstas.

una frecuencia suficiente subconjunto sería:

  • Un mapa de claves de cadena (que no contiene ningún espacio en blanco, o :=) a valores de cadena (que no contiene ningún principio o al final de espacio en blanco o saltos de línea).

En su ejemplo dir = c:\mydir, la clave sería dir y el valor c:\mydir.

Si queremos que nuestras claves y valores contengan cualquier caracter Unicode (aparte de los prohibidos), debemos usar UTF-8 (o UTF-16) como la codificación de almacenamiento, ya que no tenemos forma de escapar caracteres fuera de la codificación de almacenamiento. De lo contrario, US-ASCII o ISO-8859-1 (como archivos de propiedades normales) o cualquier otra codificación compatible con Java sería suficiente, pero asegúrese de incluir esto en su especificación del modelo de contenido (y asegúrese de leerlo de esta manera)

Como nos limitamos el modelo de contenido de manera que todos los caracteres "peligrosos" son fuera del camino, ahora podemos definir el formato de archivo simplemente como esto:

<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" > 

Cada \ que ocurre en cualquiera de las teclas o el valor ahora es una real barra invertida, nada que se escape de otra cosa. Por lo tanto, para transformarlo en el formato original, simplemente hay que doblarlo, como Grekz propuso, por ejemplo, en un lector de filtrado:

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

entonces podríamos pasar este lector a nuestro objeto Properties (o guardar el contenido a un nuevo archivo).

En su lugar, podríamos simplemente analizar este formato nosotros mismos.

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

De nuevo, usando Properties.store() después de esto, podemos exportar en el formato original.

0

No es una respuesta exacta a su pregunta, pero es una solución diferente que puede ser adecuada para sus necesidades. En Java, puede usar / como un separador de ruta y funcionará en Windows, Linux y OSX. Esto es especialmente útil para rutas relativas.

En su ejemplo, podría utilizar:

dir = c:/mydir 
+0

nuestros usuarios no sabrán usar '/' fuera de uso. si tenemos que decirles que usen '/', también podríamos haberles dicho que usen \\ – pdeva

3

Basado en @Ian Harrigan, aquí es una solución completa para obtener el archivo de propiedades de Netbeans (y archivo escapan otras propiedades) justo desde y hacia archivos de texto 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 
Cuestiones relacionadas