package org.basex.core; import static org.basex.core.Prop.HOME; import static org.basex.core.Prop.NL; import static org.basex.core.Prop.PROPHEADER; import static org.basex.core.Prop.PROPUSER; import static org.basex.util.Token.token; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Field; import java.util.Iterator; import java.util.Locale; import java.util.TreeMap; import org.basex.io.IO; import org.basex.util.Levenshtein; import org.basex.util.TokenBuilder; import org.basex.util.Util; import org.basex.util.list.StringList; /** * This class assembles properties which are used all around the project. They * are initially read from and finally written to disk. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public abstract class AProp implements Iterable<String> { /** Properties. */ protected final TreeMap<String, Object> props = new TreeMap<String, Object>(); /** Property file. */ private final String filename; /** * Constructor. * Reads the configuration file and initializes the project properties. The * file is located in the user's home directory. * If the {@code prop} argument is set to null, reading is omitted. * @param prop property file extension */ protected AProp(final String prop) { filename = HOME + IO.BASEXSUFFIX + prop; // changed Christoph Plutte if (prop != null) { } try { for(final Field f : getClass().getFields()) { final Object obj = f.get(null); if(!(obj instanceof Object[])) continue; final Object[] arr = (Object[]) obj; props.put(arr[0].toString(), arr[1]); } } catch(final Exception ex) { Util.notexpected(ex); } if(prop == null) return; final StringList read = new StringList(); final TokenBuilder err = new TokenBuilder(); final File file = new File(filename); if(!file.exists()) { err.addExt("Saving properties in \"%\"..." + NL, filename); } else { BufferedReader br = null; try { br = new BufferedReader(new FileReader(file)); for(String line; (line = br.readLine()) != null;) { line = line.trim(); if(line.isEmpty() || line.charAt(0) == '#') continue; final int d = line.indexOf('='); if(d < 0) { err.addExt("%: \"%\" ignored. " + NL, filename, line); continue; } final String val = line.substring(d + 1).trim(); String key = line.substring(0, d).trim().toUpperCase(Locale.ENGLISH); // extract numeric value in key int num = 0; final int ss = key.length(); for(int s = 0; s < ss; ++s) { if(Character.isDigit(key.charAt(s))) { num = Integer.parseInt(key.substring(s)); key = key.substring(0, s); break; } } read.add(key); final Object entry = props.get(key); if(entry == null) { err.addExt("%: \"%\" not found. " + NL, filename, key); } else if(entry instanceof String) { props.put(key, val); } else if(entry instanceof Integer) { props.put(key, Integer.parseInt(val)); } else if(entry instanceof Boolean) { props.put(key, Boolean.parseBoolean(val)); } else if(entry instanceof String[]) { if(num == 0) { props.put(key, new String[Integer.parseInt(val)]); } else { ((String[]) entry)[num - 1] = val; } } else if(entry instanceof int[]) { ((int[]) entry)[num] = Integer.parseInt(val); } } } catch(final Exception ex) { err.addExt("% could not be parsed." + NL, filename); Util.debug(ex); } finally { if(br != null) try { br.close(); } catch(final IOException ex) { } } } // check if all mandatory files have been read try { if(err.size() == 0) { boolean ok = true; for(final Field f : getClass().getFields()) { final Object obj = f.get(null); if(!(obj instanceof Object[])) continue; final String key = ((Object[]) obj)[0].toString(); ok &= read.contains(key); } if(!ok) err.addExt("Saving properties in \"%\"..." + NL, filename); } } catch(final IllegalAccessException ex) { Util.notexpected(ex); } if(err.size() != 0) { Util.err(err.toString()); write(); } } /** * Writes the properties to disk. */ public final synchronized void write() { final File file = new File(filename); final StringBuilder user = new StringBuilder(); BufferedReader br = null; try { // caches options specified by the user if(file.exists()) { br = new BufferedReader(new FileReader(file)); for(String line; (line = br.readLine()) != null;) { if(line.equals(PROPUSER)) break; } for(String line; (line = br.readLine()) != null;) { user.append(line).append(NL); } } } catch(final Exception ex) { Util.debug(ex); } finally { if(br != null) try { br.close(); } catch(final IOException e) { } } BufferedWriter bw = null; try { bw = new BufferedWriter(new FileWriter(file)); bw.write(PROPHEADER + NL); for(final Field f : getClass().getFields()) { final Object obj = f.get(null); if(!(obj instanceof Object[])) continue; final String key = ((Object[]) obj)[0].toString(); final Object val = props.get(key); if(val instanceof String[]) { final String[] str = (String[]) val; bw.write(key + " = " + str.length + NL); final int is = str.length; for(int i = 0; i < is; ++i) { if(str[i] != null) bw.write(key + (i + 1) + " = " + str[i] + NL); } } else if(val instanceof int[]) { final int[] num = (int[]) val; final int ns = num.length; for(int i = 0; i < ns; ++i) { bw.write(key + i + " = " + num[i] + NL); } } else { bw.write(key + " = " + val + NL); } } bw.write(NL + PROPUSER + NL); bw.write(user.toString()); } catch(final Exception ex) { Util.errln("% could not be written.", filename); Util.debug(ex); } finally { if(bw != null) try { bw.close(); } catch(final IOException e) { } } } /** * Returns the requested object, or {@code null}. * @param key key to be found * @return value */ public final Object get(final String key) { return props.get(key); } /** * Returns the requested string. * @param key key to be found * @return value */ public final String get(final Object[] key) { return get(key, String.class).toString(); } /** * Returns the requested integer. * @param key key to be found * @return value */ public final int num(final Object[] key) { return (Integer) get(key, Integer.class); } /** * Returns the requested boolean. * @param key key to be found * @return value */ public final boolean is(final Object[] key) { return (Boolean) get(key, Boolean.class); } /** * Returns the requested string array. * @param key key to be found * @return value */ public final String[] strings(final Object[] key) { return (String[]) get(key, String[].class); } /** * Returns the requested integer array. * @param key key to be found * @return value */ public final int[] nums(final Object[] key) { return (int[]) get(key, int[].class); } /** * Sets the specified value for the specified key. * @param key key to be found * @param val value to be written */ public final void set(final Object[] key, final String val) { setObject(key, val); } /** * Sets the specified integer for the specified key. * @param key key to be found * @param val value to be written */ public final void set(final Object[] key, final int val) { setObject(key, val); } /** * Sets the specified boolean for the specified key. * @param key key to be found * @param val value to be written */ public final void set(final Object[] key, final boolean val) { setObject(key, val); } /** * Sets the specified string array for the specified key. * @param key key to be found * @param val value to be written */ public final void set(final Object[] key, final String[] val) { setObject(key, val); } /** * Sets the specified integer array for the specified key. * @param key key to be found * @param val value to be written */ public final void set(final Object[] key, final int[] val) { setObject(key, val); } /** * Sets the specified value for the specified key. * @param key key to be found * @param val value to be written */ public final void set(final String key, final Object val) { props.put(key, val); finish(); } /** * Inverts a boolean property. * @param key key * @return new value */ public final boolean invert(final Object[] key) { final boolean val = !is(key); set(key, val); return val; } /** * Checks if the specified property has changed. * @param key key * @param val new value * @return result of check */ public final boolean sameAs(final Object[] key, final Object val) { return props.get(key[0].toString()).equals(val); } /** * Returns a key similar to the specified string, or {@code null}. * @param key key to be found * @return similar key */ public final String similar(final String key) { final byte[] name = token(key); final Levenshtein ls = new Levenshtein(); for(final String prop : props.keySet()) { if(ls.similar(name, token(prop), 0)) return prop; } return null; } /** * Retrieves the specified value. Throws an error if value cannot be read. * @param key key * @param c expected type * @return result */ private Object get(final Object[] key, final Class<?> c) { final Object entry = props.get(key[0].toString()); if(entry == null) throw Util.notexpected("Property " + key[0] + " not defined."); final Class<?> cc = entry.getClass(); if(c != cc) Util.notexpected( "Property '" + key[0] + "' is a " + Util.name(cc)); return entry; } /** * Sets the specified value. * * @param key key * @param val value */ private void setObject(final Object[] key, final Object val) { props.put(key[0].toString(), val); finish(); } /** * Sets static properties. */ void finish() { // nothing to do; if necessary, is overwritten. } @Override public String toString() { return Util.name(this) + props; } @Override public final Iterator<String> iterator() { return props.keySet().iterator(); } }