/** * Configuration * Copyright 2011 by Michael Peter Christen, mc@yacy.net, Frankfurt a. M., Germany * First released 29.06.2011 at http://yacy.net * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program in the file lgpl21.txt * If not, see <http://www.gnu.org/licenses/>. */ package net.yacy.cora.storage; 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.io.Serializable; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; import net.yacy.cora.storage.Configuration.Entry; import net.yacy.cora.storage.Files; /** * this class reads configuration attributes as a list of keywords from a list * the list may contain lines with one keyword, comment lines, empty lines and out-commented keyword lines * when an attribute is changed here, the list is stored again with the original formatting * * the syntax of configuration files: * - all lines beginning with '##' are comments * - all non-empty lines not beginning with '#' are keyword lines * - all lines beginning with '#' and where the second character is not '#' are commented-out keyword lines * - all text after a '#' not at beginn of line is treated as comment (like 'key = value # comment' ) * - a line may contain a key only or a key=value pair * @author Michael Christen */ public class Configuration extends TreeMap<String, Entry> implements Serializable { private final static long serialVersionUID=-5961730809008841258L; private final File file; protected boolean lazy; public Configuration() { this.file = null; this.lazy = false; } public Configuration(final File file) throws IOException { this.file = file; BufferedReader br = null; try { br = new BufferedReader(new FileReader(this.file)); String s; boolean enabled; String comment, key, value; int i; comment = null; while ((s = br.readLine()) != null) { if (s.startsWith("##") || s.isEmpty()){ // is comment line - do nothing if (s.startsWith("##")) comment = s.substring(2); continue; } if (s.startsWith("#")) { enabled = false ; s = s.substring (1).trim(); } else { enabled = true; } if (s.contains("#")) { // second # = text afterwards is a comment i = s.indexOf("#"); comment = s.substring(i+1); s = s.substring(0,i).trim(); } else { // comment = null; } if (s.contains("=")) { i = s.indexOf("="); key = s.substring(0,i).trim(); value = s.substring(i+1).trim(); if (value.isEmpty()) value = null; } else { key = s.trim(); value = null; } if (!key.isEmpty()) { Entry entry = new Entry(key, value, enabled); if (comment != null) { entry.setComment(comment); comment = null; } this.put(key, entry); } } } catch (final IOException e) { throw e; } finally { if (br != null) try {br.close();} catch (final IOException e) {} } } /** * override the abstract implementation because that is not stable in concurrent requests */ public boolean contains(String key) { if (key == null) return false; Entry e = this.get(key); return e == null ? false : e.enabled(); } public boolean containsDisabled(final String o) { if (o == null) return false; Entry e = this.get(o); return e == null ? false : !e.enabled(); } public boolean add(final String key) { return add(key, null); } public boolean add(final String key, final String comment) { return add(key, comment, true); } public boolean add(final String key, final String comment, final boolean enabled) { boolean modified = false; Entry entry = get(key); if (entry == null) { entry = new Entry (key,enabled); if (comment != null) entry.setComment(comment); this.put (key,entry); modified = true; } else { if (entry.enabled() != enabled) { entry.setEnable(enabled); modified = true; } if ( (comment != null) && ( !comment.equals(entry.getComment()) )) { entry.setComment(comment); modified = true; } } try { if (modified) { commit(); } } catch (final IOException e) {} return modified; } /** * save the configuration back to the file * @throws IOException */ public void commit() throws IOException { if (this.file == null) return; // create a temporary bak file, use it as template to preserve user comments File bakfile = new File (this.file.getAbsolutePath() + ".bak"); try { Files.copy(this.file, bakfile); } catch (final IOException e) { this.file.createNewFile(); } @SuppressWarnings("unchecked") TreeMap<String,Entry> tclone = (TreeMap<String,Entry>) this.clone(); // clone to write appended entries final BufferedWriter writer = new BufferedWriter(new FileWriter(this.file)); try { final BufferedReader reader = new BufferedReader(new FileReader(bakfile)); String s, sorig; String key; int i; while ((sorig = reader.readLine()) != null) { if (sorig.startsWith("##") || sorig.isEmpty()){ // is comment line - write as is writer.write(sorig + "\n"); continue; } if (sorig.startsWith("#")) { s = sorig.substring (1).trim(); } else { s = sorig; } if (s.contains("#")) { // second # = is a line comment i = s.indexOf("#"); s = s.substring(0,i).trim(); } if (s.contains("=")) { i = s.indexOf("="); key = s.substring(0,i).trim(); } else { key = s.trim(); } if (!key.isEmpty()) { Entry e = this.get(key); if (e != null) { writer.write (e.toString()); tclone.remove(key); // remove written entries from clone } writer.write("\n"); } else { writer.write(sorig+"\n"); } } reader.close(); bakfile.delete(); } catch (final IOException e) {} // write remainig entries (not already written) Iterator<Map.Entry<String,Entry>> ie = tclone.entrySet().iterator(); while (ie.hasNext()) { Object e = ie.next(); writer.write (e.toString() + "\n"); } writer.close(); } public Iterator<Entry> entryIterator() { return this.values().iterator(); } public static class Entry { private final String key; private String value; private boolean enabled; private String comment; public Entry(final String key, final boolean enabled) { this.enabled = enabled; // split in key, value if line contains a "=" (equal sign) e.g. myattribute = 123 // for backward compatibility here the key parameter is checked to contain a "=" if (key.contains("=")) { int i = key.indexOf("="); this.key = key.substring(0,i).trim(); this.value = key.substring(i+1).trim(); } else { this.key = key; this.value = null; } } public Entry (final String key, String value, final boolean enabled) { this.enabled = enabled; this.key = key; this.value = value; } public String key() { return this.key; } public void setValue(String theValue) { //empty string not wanted if ((theValue != null) && theValue.isEmpty()) { this.value = null; } else { this.value = theValue; } } public String getValue() { return this.value; } public void setComment(String comment) { this.comment = comment; } public String getComment() { return this.comment; } public void setEnable(boolean value){ this.enabled = value; } public boolean enabled() { return this.enabled; } @Override public String toString(){ // output string to write to config file return (this.enabled ? "" : "#") + (this.value != null ? this.key + " = " + this.value : this.key ) + (this.comment != null ? " #" + this.comment : ""); } } public static void main(final String[] args) { if (args.length == 0) return; final File f = new File (args[0]); Configuration cs; try { cs = new Configuration(f); Iterator<Entry> i = cs.entryIterator(); Entry k; System.out.println("\nall activated attributes:"); while (i.hasNext()) { k = i.next(); if (k.enabled()) System.out.println(k.toString()); } i = cs.entryIterator(); System.out.println("\nall deactivated attributes:"); while (i.hasNext()) { k = i.next(); if (!k.enabled()) System.out.println(k.toString() ); } } catch (final IOException e) { e.printStackTrace(); } } }