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 }