Mercurial > jhg
comparison src/org/tmatesoft/hg/internal/ConfigFile.java @ 483:e31e85cf4d4c
Handle include and unset directives in config files
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> | 
|---|---|
| date | Tue, 07 Aug 2012 19:14:53 +0200 | 
| parents | 9fb990c8a724 | 
| children | db48a77ec8ff | 
   comparison
  equal
  deleted
  inserted
  replaced
| 482:6c67debed07e | 483:e31e85cf4d4c | 
|---|---|
| 1 /* | 1 /* | 
| 2 * Copyright (c) 2011 TMate Software Ltd | 2 * Copyright (c) 2011-2012 TMate Software Ltd | 
| 3 * | 3 * | 
| 4 * This program is free software; you can redistribute it and/or modify | 4 * This program is free software; you can redistribute it and/or modify | 
| 5 * it under the terms of the GNU General Public License as published by | 5 * it under the terms of the GNU General Public License as published by | 
| 6 * the Free Software Foundation; version 2 of the License. | 6 * the Free Software Foundation; version 2 of the License. | 
| 7 * | 7 * | 
| 14 * the terms of a license other than GNU General Public License | 14 * the terms of a license other than GNU General Public License | 
| 15 * contact TMate Software at support@hg4j.com | 15 * contact TMate Software at support@hg4j.com | 
| 16 */ | 16 */ | 
| 17 package org.tmatesoft.hg.internal; | 17 package org.tmatesoft.hg.internal; | 
| 18 | 18 | 
| 19 import java.io.BufferedReader; | |
| 20 import java.io.ByteArrayOutputStream; | 19 import java.io.ByteArrayOutputStream; | 
| 21 import java.io.File; | 20 import java.io.File; | 
| 22 import java.io.FileOutputStream; | 21 import java.io.FileOutputStream; | 
| 23 import java.io.FileReader; | |
| 24 import java.io.IOException; | 22 import java.io.IOException; | 
| 25 import java.io.PrintStream; | 23 import java.io.PrintStream; | 
| 26 import java.nio.ByteBuffer; | 24 import java.nio.ByteBuffer; | 
| 27 import java.nio.channels.FileChannel; | 25 import java.nio.channels.FileChannel; | 
| 28 import java.nio.channels.FileLock; | 26 import java.nio.channels.FileLock; | 
| 31 import java.util.Iterator; | 29 import java.util.Iterator; | 
| 32 import java.util.LinkedHashMap; | 30 import java.util.LinkedHashMap; | 
| 33 import java.util.List; | 31 import java.util.List; | 
| 34 import java.util.Map; | 32 import java.util.Map; | 
| 35 | 33 | 
| 34 import org.tmatesoft.hg.core.SessionContext; | |
| 35 import org.tmatesoft.hg.repo.HgInvalidFileException; | |
| 36 import org.tmatesoft.hg.util.LogFacility; | |
| 37 | |
| 36 /** | 38 /** | 
| 37 * .ini / .rc file reader | 39 * .ini / .rc file reader | 
| 38 * @author Artem Tikhomirov | 40 * @author Artem Tikhomirov | 
| 39 * @author TMate Software Ltd. | 41 * @author TMate Software Ltd. | 
| 40 */ | 42 */ | 
| 41 public class ConfigFile { | 43 public class ConfigFile { | 
| 42 | 44 | 
| 45 private final SessionContext sessionContext; | |
| 43 private List<String> sections; | 46 private List<String> sections; | 
| 44 private List<Map<String,String>> content; | 47 private List<Map<String,String>> content; | 
| 45 | 48 | 
| 46 public ConfigFile() { | 49 public ConfigFile(SessionContext ctx) { | 
| 47 } | 50 sessionContext = ctx; | 
| 48 | 51 } | 
| 49 public void addLocation(File path) throws IOException { | 52 | 
| 53 public void addLocation(File path) throws HgInvalidFileException { | |
| 50 read(path); | 54 read(path); | 
| 51 } | 55 } | 
| 52 | 56 | 
| 53 public boolean hasSection(String sectionName) { | 57 public boolean hasSection(String sectionName) { | 
| 54 return sections == null ? false : sections.indexOf(sectionName) != -1; | 58 return sections == null ? false : sections.indexOf(sectionName) != -1; | 
| 109 } else { | 113 } else { | 
| 110 section.put(key, newValue); | 114 section.put(key, newValue); | 
| 111 } | 115 } | 
| 112 } | 116 } | 
| 113 | 117 | 
| 114 // TODO handle %include and %unset directives | 118 private void read(File f) throws HgInvalidFileException { | 
| 115 // TODO "" and lists | |
| 116 // XXX perhaps, single string to keep whole section with substrings for keys/values to minimize number of arrays (String.value) | |
| 117 private void read(File f) throws IOException { | |
| 118 if (f == null || !f.canRead()) { | 119 if (f == null || !f.canRead()) { | 
| 119 return; | 120 return; | 
| 120 } | 121 } | 
| 121 if (sections == null) { | 122 if (sections == null) { | 
| 122 sections = new ArrayList<String>(); | 123 sections = new ArrayList<String>(); | 
| 123 content = new ArrayList<Map<String,String>>(); | 124 content = new ArrayList<Map<String,String>>(); | 
| 124 } | 125 } | 
| 125 BufferedReader br = null; | 126 new Parser().go(f, this); | 
| 126 try { | 127 ((ArrayList<?>) sections).trimToSize(); | 
| 127 br = new BufferedReader(new FileReader(f)); | 128 ((ArrayList<?>) content).trimToSize(); | 
| 128 String line; | |
| 129 String sectionName = ""; | |
| 130 Map<String,String> section = new LinkedHashMap<String, String>(); | |
| 131 while ((line = br.readLine()) != null) { | |
| 132 line = line.trim(); | |
| 133 int x; | |
| 134 if ((x = line.indexOf('#')) != -1) { | |
| 135 // do not keep comments in memory, get new, shorter string | |
| 136 line = new String(line.substring(0, x).trim()); | |
| 137 } | |
| 138 if (line.length() <= 2) { // a=b or [a] are at least of length 3 | |
| 139 continue; | |
| 140 } | |
| 141 if (line.charAt(0) == '[' && line.charAt(line.length() - 1) == ']') { | |
| 142 sectionName = line.substring(1, line.length() - 1); | |
| 143 if (sections.indexOf(sectionName) == -1) { | |
| 144 sections.add(sectionName); | |
| 145 content.add(section = new LinkedHashMap<String, String>()); | |
| 146 } else { | |
| 147 section = null; // drop cached value | |
| 148 } | |
| 149 } else if ((x = line.indexOf('=')) != -1) { | |
| 150 // share char[] of the original string | |
| 151 String key = line.substring(0, x).trim(); | |
| 152 String value = line.substring(x+1).trim(); | |
| 153 if (section == null) { | |
| 154 int i = sections.indexOf(sectionName); | |
| 155 assert i >= 0; | |
| 156 section = content.get(i); | |
| 157 } | |
| 158 if (sectionName.length() == 0) { | |
| 159 // add fake section only if there are any values | |
| 160 sections.add(sectionName); | |
| 161 content.add(section); | |
| 162 } | |
| 163 section.put(key, value); | |
| 164 } | |
| 165 } | |
| 166 } finally { | |
| 167 ((ArrayList<?>) sections).trimToSize(); | |
| 168 ((ArrayList<?>) content).trimToSize(); | |
| 169 if (br != null) { | |
| 170 br.close(); | |
| 171 } | |
| 172 } | |
| 173 assert sections.size() == content.size(); | 129 assert sections.size() == content.size(); | 
| 174 } | 130 } | 
| 175 | 131 | 
| 176 public void writeTo(File f) throws IOException { | 132 public void writeTo(File f) throws IOException { | 
| 177 byte[] data = compose(); | 133 byte[] data = compose(); | 
| 208 ps.println(); | 164 ps.println(); | 
| 209 } | 165 } | 
| 210 ps.flush(); | 166 ps.flush(); | 
| 211 return bos.toByteArray(); | 167 return bos.toByteArray(); | 
| 212 } | 168 } | 
| 169 | |
| 170 private static class Parser implements LineReader.LineConsumer<ConfigFile> { | |
| 171 | |
| 172 private String sectionName = ""; | |
| 173 private Map<String,String> section = new LinkedHashMap<String, String>(); | |
| 174 private File contextFile; | |
| 175 | |
| 176 // TODO "" and lists | |
| 177 // XXX perhaps, single string to keep whole section with substrings for keys/values to minimize number of arrays (String.value) | |
| 178 public boolean consume(String line, ConfigFile cfg) throws IOException { | |
| 179 int x; | |
| 180 if ((x = line.indexOf('#')) != -1) { | |
| 181 // do not keep comments in memory, get new, shorter string | |
| 182 line = new String(line.substring(0, x).trim()); | |
| 183 } | |
| 184 if (line.length() <= 2) { // a=b or [a] are at least of length 3 | |
| 185 return true; | |
| 186 } | |
| 187 if (line.charAt(0) == '[' && line.charAt(line.length() - 1) == ']') { | |
| 188 sectionName = line.substring(1, line.length() - 1); | |
| 189 if (cfg.sections.indexOf(sectionName) == -1) { | |
| 190 cfg.sections.add(sectionName); | |
| 191 cfg.content.add(section = new LinkedHashMap<String, String>()); | |
| 192 } else { | |
| 193 section = null; // drop cached value | |
| 194 } | |
| 195 } else if (line.startsWith("%unset")) { | |
| 196 if (section != null) { | |
| 197 section.remove(line.substring(7).trim()); | |
| 198 } | |
| 199 } else if (line.startsWith("%include")) { | |
| 200 processInclude(line.substring(9).trim(), cfg); | |
| 201 } else if ((x = line.indexOf('=')) != -1) { | |
| 202 // share char[] of the original string | |
| 203 String key = line.substring(0, x).trim(); | |
| 204 String value = line.substring(x+1).trim(); | |
| 205 if (section == null) { | |
| 206 int i = cfg.sections.indexOf(sectionName); | |
| 207 assert i >= 0; | |
| 208 section = cfg.content.get(i); | |
| 209 } | |
| 210 if (sectionName.length() == 0) { | |
| 211 // add fake section only if there are any values | |
| 212 cfg.sections.add(sectionName); | |
| 213 cfg.content.add(section); | |
| 214 } | |
| 215 section.put(key, value); | |
| 216 } | |
| 217 return true; | |
| 218 } | |
| 219 | |
| 220 public void go(File f, ConfigFile cfg) throws HgInvalidFileException { | |
| 221 contextFile = f; | |
| 222 LineReader lr = new LineReader(f, cfg.sessionContext.getLog()); | |
| 223 lr.ignoreLineComments("#"); | |
| 224 lr.read(this, cfg); | |
| 225 } | |
| 226 | |
| 227 // include failure doesn't propagate | |
| 228 private void processInclude(String includeValue, ConfigFile cfg) { | |
| 229 File f; | |
| 230 // TODO handle environment variable expansion | |
| 231 if (includeValue.startsWith("~/")) { | |
| 232 f = new File(System.getProperty("user.home"), includeValue.substring(2)); | |
| 233 } else { | |
| 234 f = new File(contextFile.getParentFile(), includeValue); | |
| 235 } | |
| 236 try { | |
| 237 if (f.canRead()) { | |
| 238 new Parser().go(f, cfg); | |
| 239 } else { | |
| 240 LogFacility lf = cfg.sessionContext.getLog(); | |
| 241 lf.dump(ConfigFile.class, LogFacility.Severity.Debug, "Can't read file to include: %s", f); | |
| 242 } | |
| 243 } catch (HgInvalidFileException ex) { | |
| 244 LogFacility lf = cfg.sessionContext.getLog(); | |
| 245 lf.dump(ConfigFile.class, LogFacility.Severity.Warn, "Can't include %s (%s)", f, includeValue); | |
| 246 } | |
| 247 } | |
| 248 } | |
| 213 } | 249 } | 
