You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Config.java 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  1. /*
  2. * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
  3. * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  4. * Copyright (C) 2008-2010, Google Inc.
  5. * Copyright (C) 2009, Google, Inc.
  6. * Copyright (C) 2009, JetBrains s.r.o.
  7. * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  8. * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
  9. * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com>
  10. * and other copyright owners as documented in the project's IP log.
  11. *
  12. * This program and the accompanying materials are made available
  13. * under the terms of the Eclipse Distribution License v1.0 which
  14. * accompanies this distribution, is reproduced below, and is
  15. * available at http://www.eclipse.org/org/documents/edl-v10.php
  16. *
  17. * All rights reserved.
  18. *
  19. * Redistribution and use in source and binary forms, with or
  20. * without modification, are permitted provided that the following
  21. * conditions are met:
  22. *
  23. * - Redistributions of source code must retain the above copyright
  24. * notice, this list of conditions and the following disclaimer.
  25. *
  26. * - Redistributions in binary form must reproduce the above
  27. * copyright notice, this list of conditions and the following
  28. * disclaimer in the documentation and/or other materials provided
  29. * with the distribution.
  30. *
  31. * - Neither the name of the Eclipse Foundation, Inc. nor the
  32. * names of its contributors may be used to endorse or promote
  33. * products derived from this software without specific prior
  34. * written permission.
  35. *
  36. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  37. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  38. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  39. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  40. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  41. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  42. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  43. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  44. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  45. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  46. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  47. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  48. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  49. */
  50. package org.eclipse.jgit.lib;
  51. import java.util.ArrayList;
  52. import java.util.Collections;
  53. import java.util.HashSet;
  54. import java.util.List;
  55. import java.util.Map;
  56. import java.util.Set;
  57. import java.util.concurrent.ConcurrentHashMap;
  58. import java.util.concurrent.atomic.AtomicReference;
  59. import org.eclipse.jgit.errors.ConfigInvalidException;
  60. import org.eclipse.jgit.util.StringUtils;
  61. /**
  62. * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.
  63. */
  64. public class Config {
  65. private static final String[] EMPTY_STRING_ARRAY = {};
  66. private static final long KiB = 1024;
  67. private static final long MiB = 1024 * KiB;
  68. private static final long GiB = 1024 * MiB;
  69. /**
  70. * Immutable current state of the configuration data.
  71. * <p>
  72. * This state is copy-on-write. It should always contain an immutable list
  73. * of the configuration keys/values.
  74. */
  75. private final AtomicReference<State> state;
  76. private final Config baseConfig;
  77. /**
  78. * Magic value indicating a missing entry.
  79. * <p>
  80. * This value is tested for reference equality in some contexts, so we
  81. * must ensure it is a special copy of the empty string. It also must
  82. * be treated like the empty string.
  83. */
  84. private static final String MAGIC_EMPTY_VALUE = new String();
  85. /** Create a configuration with no default fallback. */
  86. public Config() {
  87. this(null);
  88. }
  89. /**
  90. * Create an empty configuration with a fallback for missing keys.
  91. *
  92. * @param defaultConfig
  93. * the base configuration to be consulted when a key is missing
  94. * from this configuration instance.
  95. */
  96. public Config(Config defaultConfig) {
  97. baseConfig = defaultConfig;
  98. state = new AtomicReference<State>(newState());
  99. }
  100. /**
  101. * Escape the value before saving
  102. *
  103. * @param x
  104. * the value to escape
  105. * @return the escaped value
  106. */
  107. private static String escapeValue(final String x) {
  108. boolean inquote = false;
  109. int lineStart = 0;
  110. final StringBuffer r = new StringBuffer(x.length());
  111. for (int k = 0; k < x.length(); k++) {
  112. final char c = x.charAt(k);
  113. switch (c) {
  114. case '\n':
  115. if (inquote) {
  116. r.append('"');
  117. inquote = false;
  118. }
  119. r.append("\\n\\\n");
  120. lineStart = r.length();
  121. break;
  122. case '\t':
  123. r.append("\\t");
  124. break;
  125. case '\b':
  126. r.append("\\b");
  127. break;
  128. case '\\':
  129. r.append("\\\\");
  130. break;
  131. case '"':
  132. r.append("\\\"");
  133. break;
  134. case ';':
  135. case '#':
  136. if (!inquote) {
  137. r.insert(lineStart, '"');
  138. inquote = true;
  139. }
  140. r.append(c);
  141. break;
  142. case ' ':
  143. if (!inquote && r.length() > 0
  144. && r.charAt(r.length() - 1) == ' ') {
  145. r.insert(lineStart, '"');
  146. inquote = true;
  147. }
  148. r.append(' ');
  149. break;
  150. default:
  151. r.append(c);
  152. break;
  153. }
  154. }
  155. if (inquote) {
  156. r.append('"');
  157. }
  158. return r.toString();
  159. }
  160. /**
  161. * Obtain an integer value from the configuration.
  162. *
  163. * @param section
  164. * section the key is grouped within.
  165. * @param name
  166. * name of the key to get.
  167. * @param defaultValue
  168. * default value to return if no value was present.
  169. * @return an integer value from the configuration, or defaultValue.
  170. */
  171. public int getInt(final String section, final String name,
  172. final int defaultValue) {
  173. return getInt(section, null, name, defaultValue);
  174. }
  175. /**
  176. * Obtain an integer value from the configuration.
  177. *
  178. * @param section
  179. * section the key is grouped within.
  180. * @param subsection
  181. * subsection name, such a remote or branch name.
  182. * @param name
  183. * name of the key to get.
  184. * @param defaultValue
  185. * default value to return if no value was present.
  186. * @return an integer value from the configuration, or defaultValue.
  187. */
  188. public int getInt(final String section, String subsection,
  189. final String name, final int defaultValue) {
  190. final long val = getLong(section, subsection, name, defaultValue);
  191. if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE)
  192. return (int) val;
  193. throw new IllegalArgumentException("Integer value " + section + "."
  194. + name + " out of range");
  195. }
  196. /**
  197. * Obtain an integer value from the configuration.
  198. *
  199. * @param section
  200. * section the key is grouped within.
  201. * @param subsection
  202. * subsection name, such a remote or branch name.
  203. * @param name
  204. * name of the key to get.
  205. * @param defaultValue
  206. * default value to return if no value was present.
  207. * @return an integer value from the configuration, or defaultValue.
  208. */
  209. public long getLong(final String section, String subsection,
  210. final String name, final long defaultValue) {
  211. final String str = getString(section, subsection, name);
  212. if (str == null)
  213. return defaultValue;
  214. String n = str.trim();
  215. if (n.length() == 0)
  216. return defaultValue;
  217. long mul = 1;
  218. switch (StringUtils.toLowerCase(n.charAt(n.length() - 1))) {
  219. case 'g':
  220. mul = GiB;
  221. break;
  222. case 'm':
  223. mul = MiB;
  224. break;
  225. case 'k':
  226. mul = KiB;
  227. break;
  228. }
  229. if (mul > 1)
  230. n = n.substring(0, n.length() - 1).trim();
  231. if (n.length() == 0)
  232. return defaultValue;
  233. try {
  234. return mul * Long.parseLong(n);
  235. } catch (NumberFormatException nfe) {
  236. throw new IllegalArgumentException("Invalid integer value: "
  237. + section + "." + name + "=" + str);
  238. }
  239. }
  240. /**
  241. * Get a boolean value from the git config
  242. *
  243. * @param section
  244. * section the key is grouped within.
  245. * @param name
  246. * name of the key to get.
  247. * @param defaultValue
  248. * default value to return if no value was present.
  249. * @return true if any value or defaultValue is true, false for missing or
  250. * explicit false
  251. */
  252. public boolean getBoolean(final String section, final String name,
  253. final boolean defaultValue) {
  254. return getBoolean(section, null, name, defaultValue);
  255. }
  256. /**
  257. * Get a boolean value from the git config
  258. *
  259. * @param section
  260. * section the key is grouped within.
  261. * @param subsection
  262. * subsection name, such a remote or branch name.
  263. * @param name
  264. * name of the key to get.
  265. * @param defaultValue
  266. * default value to return if no value was present.
  267. * @return true if any value or defaultValue is true, false for missing or
  268. * explicit false
  269. */
  270. public boolean getBoolean(final String section, String subsection,
  271. final String name, final boolean defaultValue) {
  272. String n = getRawString(section, subsection, name);
  273. if (n == null)
  274. return defaultValue;
  275. if (MAGIC_EMPTY_VALUE == n)
  276. return true;
  277. try {
  278. return StringUtils.toBoolean(n);
  279. } catch (IllegalArgumentException err) {
  280. throw new IllegalArgumentException("Invalid boolean value: "
  281. + section + "." + name + "=" + n);
  282. }
  283. }
  284. /**
  285. * Get string value
  286. *
  287. * @param section
  288. * the section
  289. * @param subsection
  290. * the subsection for the value
  291. * @param name
  292. * the key name
  293. * @return a String value from git config.
  294. */
  295. public String getString(final String section, String subsection,
  296. final String name) {
  297. return getRawString(section, subsection, name);
  298. }
  299. /**
  300. * Get a list of string values
  301. * <p>
  302. * If this instance was created with a base, the base's values are returned
  303. * first (if any).
  304. *
  305. * @param section
  306. * the section
  307. * @param subsection
  308. * the subsection for the value
  309. * @param name
  310. * the key name
  311. * @return array of zero or more values from the configuration.
  312. */
  313. public String[] getStringList(final String section, String subsection,
  314. final String name) {
  315. final String[] baseList;
  316. if (baseConfig != null)
  317. baseList = baseConfig.getStringList(section, subsection, name);
  318. else
  319. baseList = EMPTY_STRING_ARRAY;
  320. final List<String> lst = getRawStringList(section, subsection, name);
  321. if (lst != null) {
  322. final String[] res = new String[baseList.length + lst.size()];
  323. int idx = baseList.length;
  324. System.arraycopy(baseList, 0, res, 0, idx);
  325. for (final String val : lst)
  326. res[idx++] = val;
  327. return res;
  328. }
  329. return baseList;
  330. }
  331. /**
  332. * @param section
  333. * section to search for.
  334. * @return set of all subsections of specified section within this
  335. * configuration and its base configuration; may be empty if no
  336. * subsection exists.
  337. */
  338. public Set<String> getSubsections(final String section) {
  339. return get(new SubsectionNames(section));
  340. }
  341. /**
  342. * Obtain a handle to a parsed set of configuration values.
  343. *
  344. * @param <T>
  345. * type of configuration model to return.
  346. * @param parser
  347. * parser which can create the model if it is not already
  348. * available in this configuration file. The parser is also used
  349. * as the key into a cache and must obey the hashCode and equals
  350. * contract in order to reuse a parsed model.
  351. * @return the parsed object instance, which is cached inside this config.
  352. */
  353. @SuppressWarnings("unchecked")
  354. public <T> T get(final SectionParser<T> parser) {
  355. final State myState = getState();
  356. T obj = (T) myState.cache.get(parser);
  357. if (obj == null) {
  358. obj = parser.parse(this);
  359. myState.cache.put(parser, obj);
  360. }
  361. return obj;
  362. }
  363. /**
  364. * Remove a cached configuration object.
  365. * <p>
  366. * If the associated configuration object has not yet been cached, this
  367. * method has no effect.
  368. *
  369. * @param parser
  370. * parser used to obtain the configuration object.
  371. * @see #get(SectionParser)
  372. */
  373. public void uncache(final SectionParser<?> parser) {
  374. state.get().cache.remove(parser);
  375. }
  376. private String getRawString(final String section, final String subsection,
  377. final String name) {
  378. final List<String> lst = getRawStringList(section, subsection, name);
  379. if (lst != null)
  380. return lst.get(0);
  381. else if (baseConfig != null)
  382. return baseConfig.getRawString(section, subsection, name);
  383. else
  384. return null;
  385. }
  386. private List<String> getRawStringList(final String section,
  387. final String subsection, final String name) {
  388. List<String> r = null;
  389. for (final Entry e : state.get().entryList) {
  390. if (e.match(section, subsection, name))
  391. r = add(r, e.value);
  392. }
  393. return r;
  394. }
  395. private static List<String> add(final List<String> curr, final String value) {
  396. if (curr == null)
  397. return Collections.singletonList(value);
  398. if (curr.size() == 1) {
  399. final List<String> r = new ArrayList<String>(2);
  400. r.add(curr.get(0));
  401. r.add(value);
  402. return r;
  403. }
  404. curr.add(value);
  405. return curr;
  406. }
  407. private State getState() {
  408. State cur, upd;
  409. do {
  410. cur = state.get();
  411. final State base = getBaseState();
  412. if (cur.baseState == base)
  413. return cur;
  414. upd = new State(cur.entryList, base);
  415. } while (!state.compareAndSet(cur, upd));
  416. return upd;
  417. }
  418. private State getBaseState() {
  419. return baseConfig != null ? baseConfig.getState() : null;
  420. }
  421. /**
  422. * Add or modify a configuration value. The parameters will result in a
  423. * configuration entry like this.
  424. *
  425. * <pre>
  426. * [section &quot;subsection&quot;]
  427. * name = value
  428. * </pre>
  429. *
  430. * @param section
  431. * section name, e.g "branch"
  432. * @param subsection
  433. * optional subsection value, e.g. a branch name
  434. * @param name
  435. * parameter name, e.g. "filemode"
  436. * @param value
  437. * parameter value
  438. */
  439. public void setInt(final String section, final String subsection,
  440. final String name, final int value) {
  441. setLong(section, subsection, name, value);
  442. }
  443. /**
  444. * Add or modify a configuration value. The parameters will result in a
  445. * configuration entry like this.
  446. *
  447. * <pre>
  448. * [section &quot;subsection&quot;]
  449. * name = value
  450. * </pre>
  451. *
  452. * @param section
  453. * section name, e.g "branch"
  454. * @param subsection
  455. * optional subsection value, e.g. a branch name
  456. * @param name
  457. * parameter name, e.g. "filemode"
  458. * @param value
  459. * parameter value
  460. */
  461. public void setLong(final String section, final String subsection,
  462. final String name, final long value) {
  463. final String s;
  464. if (value >= GiB && (value % GiB) == 0)
  465. s = String.valueOf(value / GiB) + " g";
  466. else if (value >= MiB && (value % MiB) == 0)
  467. s = String.valueOf(value / MiB) + " m";
  468. else if (value >= KiB && (value % KiB) == 0)
  469. s = String.valueOf(value / KiB) + " k";
  470. else
  471. s = String.valueOf(value);
  472. setString(section, subsection, name, s);
  473. }
  474. /**
  475. * Add or modify a configuration value. The parameters will result in a
  476. * configuration entry like this.
  477. *
  478. * <pre>
  479. * [section &quot;subsection&quot;]
  480. * name = value
  481. * </pre>
  482. *
  483. * @param section
  484. * section name, e.g "branch"
  485. * @param subsection
  486. * optional subsection value, e.g. a branch name
  487. * @param name
  488. * parameter name, e.g. "filemode"
  489. * @param value
  490. * parameter value
  491. */
  492. public void setBoolean(final String section, final String subsection,
  493. final String name, final boolean value) {
  494. setString(section, subsection, name, value ? "true" : "false");
  495. }
  496. /**
  497. * Add or modify a configuration value. The parameters will result in a
  498. * configuration entry like this.
  499. *
  500. * <pre>
  501. * [section &quot;subsection&quot;]
  502. * name = value
  503. * </pre>
  504. *
  505. * @param section
  506. * section name, e.g "branch"
  507. * @param subsection
  508. * optional subsection value, e.g. a branch name
  509. * @param name
  510. * parameter name, e.g. "filemode"
  511. * @param value
  512. * parameter value, e.g. "true"
  513. */
  514. public void setString(final String section, final String subsection,
  515. final String name, final String value) {
  516. setStringList(section, subsection, name, Collections
  517. .singletonList(value));
  518. }
  519. /**
  520. * Remove a configuration value.
  521. *
  522. * @param section
  523. * section name, e.g "branch"
  524. * @param subsection
  525. * optional subsection value, e.g. a branch name
  526. * @param name
  527. * parameter name, e.g. "filemode"
  528. */
  529. public void unset(final String section, final String subsection,
  530. final String name) {
  531. setStringList(section, subsection, name, Collections
  532. .<String> emptyList());
  533. }
  534. /**
  535. * Remove all configuration values under a single section.
  536. *
  537. * @param section
  538. * section name, e.g "branch"
  539. * @param subsection
  540. * optional subsection value, e.g. a branch name
  541. */
  542. public void unsetSection(String section, String subsection) {
  543. State src, res;
  544. do {
  545. src = state.get();
  546. res = unsetSection(src, section, subsection);
  547. } while (!state.compareAndSet(src, res));
  548. }
  549. private State unsetSection(final State srcState, final String section,
  550. final String subsection) {
  551. final int max = srcState.entryList.size();
  552. final ArrayList<Entry> r = new ArrayList<Entry>(max);
  553. boolean lastWasMatch = false;
  554. for (Entry e : srcState.entryList) {
  555. if (e.match(section, subsection)) {
  556. // Skip this record, it's for the section we are removing.
  557. lastWasMatch = true;
  558. continue;
  559. }
  560. if (lastWasMatch && e.section == null && e.subsection == null)
  561. continue; // skip this padding line in the section.
  562. r.add(e);
  563. }
  564. return newState(r);
  565. }
  566. /**
  567. * Set a configuration value.
  568. *
  569. * <pre>
  570. * [section &quot;subsection&quot;]
  571. * name = value
  572. * </pre>
  573. *
  574. * @param section
  575. * section name, e.g "branch"
  576. * @param subsection
  577. * optional subsection value, e.g. a branch name
  578. * @param name
  579. * parameter name, e.g. "filemode"
  580. * @param values
  581. * list of zero or more values for this key.
  582. */
  583. public void setStringList(final String section, final String subsection,
  584. final String name, final List<String> values) {
  585. State src, res;
  586. do {
  587. src = state.get();
  588. res = replaceStringList(src, section, subsection, name, values);
  589. } while (!state.compareAndSet(src, res));
  590. }
  591. private State replaceStringList(final State srcState,
  592. final String section, final String subsection, final String name,
  593. final List<String> values) {
  594. final List<Entry> entries = copy(srcState, values);
  595. int entryIndex = 0;
  596. int valueIndex = 0;
  597. int insertPosition = -1;
  598. // Reset the first n Entry objects that match this input name.
  599. //
  600. while (entryIndex < entries.size() && valueIndex < values.size()) {
  601. final Entry e = entries.get(entryIndex);
  602. if (e.match(section, subsection, name)) {
  603. entries.set(entryIndex, e.forValue(values.get(valueIndex++)));
  604. insertPosition = entryIndex + 1;
  605. }
  606. entryIndex++;
  607. }
  608. // Remove any extra Entry objects that we no longer need.
  609. //
  610. if (valueIndex == values.size() && entryIndex < entries.size()) {
  611. while (entryIndex < entries.size()) {
  612. final Entry e = entries.get(entryIndex++);
  613. if (e.match(section, subsection, name))
  614. entries.remove(--entryIndex);
  615. }
  616. }
  617. // Insert new Entry objects for additional/new values.
  618. //
  619. if (valueIndex < values.size() && entryIndex == entries.size()) {
  620. if (insertPosition < 0) {
  621. // We didn't find a matching key above, but maybe there
  622. // is already a section available that matches. Insert
  623. // after the last key of that section.
  624. //
  625. insertPosition = findSectionEnd(entries, section, subsection);
  626. }
  627. if (insertPosition < 0) {
  628. // We didn't find any matching section header for this key,
  629. // so we must create a new section header at the end.
  630. //
  631. final Entry e = new Entry();
  632. e.section = section;
  633. e.subsection = subsection;
  634. entries.add(e);
  635. insertPosition = entries.size();
  636. }
  637. while (valueIndex < values.size()) {
  638. final Entry e = new Entry();
  639. e.section = section;
  640. e.subsection = subsection;
  641. e.name = name;
  642. e.value = values.get(valueIndex++);
  643. entries.add(insertPosition++, e);
  644. }
  645. }
  646. return newState(entries);
  647. }
  648. private static List<Entry> copy(final State src, final List<String> values) {
  649. // At worst we need to insert 1 line for each value, plus 1 line
  650. // for a new section header. Assume that and allocate the space.
  651. //
  652. final int max = src.entryList.size() + values.size() + 1;
  653. final ArrayList<Entry> r = new ArrayList<Entry>(max);
  654. r.addAll(src.entryList);
  655. return r;
  656. }
  657. private static int findSectionEnd(final List<Entry> entries,
  658. final String section, final String subsection) {
  659. for (int i = 0; i < entries.size(); i++) {
  660. Entry e = entries.get(i);
  661. if (e.match(section, subsection, null)) {
  662. i++;
  663. while (i < entries.size()) {
  664. e = entries.get(i);
  665. if (e.match(section, subsection, e.name))
  666. i++;
  667. else
  668. break;
  669. }
  670. return i;
  671. }
  672. }
  673. return -1;
  674. }
  675. /**
  676. * @return this configuration, formatted as a Git style text file.
  677. */
  678. public String toText() {
  679. final StringBuilder out = new StringBuilder();
  680. for (final Entry e : state.get().entryList) {
  681. if (e.prefix != null)
  682. out.append(e.prefix);
  683. if (e.section != null && e.name == null) {
  684. out.append('[');
  685. out.append(e.section);
  686. if (e.subsection != null) {
  687. out.append(' ');
  688. out.append('"');
  689. out.append(escapeValue(e.subsection));
  690. out.append('"');
  691. }
  692. out.append(']');
  693. } else if (e.section != null && e.name != null) {
  694. if (e.prefix == null || "".equals(e.prefix))
  695. out.append('\t');
  696. out.append(e.name);
  697. if (MAGIC_EMPTY_VALUE != e.value) {
  698. out.append(" =");
  699. if (e.value != null) {
  700. out.append(' ');
  701. out.append(escapeValue(e.value));
  702. }
  703. }
  704. if (e.suffix != null)
  705. out.append(' ');
  706. }
  707. if (e.suffix != null)
  708. out.append(e.suffix);
  709. out.append('\n');
  710. }
  711. return out.toString();
  712. }
  713. /**
  714. * Clear this configuration and reset to the contents of the parsed string.
  715. *
  716. * @param text
  717. * Git style text file listing configuration properties.
  718. * @throws ConfigInvalidException
  719. * the text supplied is not formatted correctly. No changes were
  720. * made to {@code this}.
  721. */
  722. public void fromText(final String text) throws ConfigInvalidException {
  723. final List<Entry> newEntries = new ArrayList<Entry>();
  724. final StringReader in = new StringReader(text);
  725. Entry last = null;
  726. Entry e = new Entry();
  727. for (;;) {
  728. int input = in.read();
  729. if (-1 == input)
  730. break;
  731. final char c = (char) input;
  732. if ('\n' == c) {
  733. // End of this entry.
  734. newEntries.add(e);
  735. if (e.section != null)
  736. last = e;
  737. e = new Entry();
  738. } else if (e.suffix != null) {
  739. // Everything up until the end-of-line is in the suffix.
  740. e.suffix += c;
  741. } else if (';' == c || '#' == c) {
  742. // The rest of this line is a comment; put into suffix.
  743. e.suffix = String.valueOf(c);
  744. } else if (e.section == null && Character.isWhitespace(c)) {
  745. // Save the leading whitespace (if any).
  746. if (e.prefix == null)
  747. e.prefix = "";
  748. e.prefix += c;
  749. } else if ('[' == c) {
  750. // This is a section header.
  751. e.section = readSectionName(in);
  752. input = in.read();
  753. if ('"' == input) {
  754. e.subsection = readValue(in, true, '"');
  755. input = in.read();
  756. }
  757. if (']' != input)
  758. throw new ConfigInvalidException("Bad group header");
  759. e.suffix = "";
  760. } else if (last != null) {
  761. // Read a value.
  762. e.section = last.section;
  763. e.subsection = last.subsection;
  764. in.reset();
  765. e.name = readKeyName(in);
  766. if (e.name.endsWith("\n")) {
  767. e.name = e.name.substring(0, e.name.length() - 1);
  768. e.value = MAGIC_EMPTY_VALUE;
  769. } else
  770. e.value = readValue(in, false, -1);
  771. } else
  772. throw new ConfigInvalidException("Invalid line in config file");
  773. }
  774. state.set(newState(newEntries));
  775. }
  776. private State newState() {
  777. return new State(Collections.<Entry> emptyList(), getBaseState());
  778. }
  779. private State newState(final List<Entry> entries) {
  780. return new State(Collections.unmodifiableList(entries), getBaseState());
  781. }
  782. /**
  783. * Clear the configuration file
  784. */
  785. protected void clear() {
  786. state.set(newState());
  787. }
  788. private static String readSectionName(final StringReader in)
  789. throws ConfigInvalidException {
  790. final StringBuilder name = new StringBuilder();
  791. for (;;) {
  792. int c = in.read();
  793. if (c < 0)
  794. throw new ConfigInvalidException("Unexpected end of config file");
  795. if (']' == c) {
  796. in.reset();
  797. break;
  798. }
  799. if (' ' == c || '\t' == c) {
  800. for (;;) {
  801. c = in.read();
  802. if (c < 0)
  803. throw new ConfigInvalidException("Unexpected end of config file");
  804. if ('"' == c) {
  805. in.reset();
  806. break;
  807. }
  808. if (' ' == c || '\t' == c)
  809. continue; // Skipped...
  810. throw new ConfigInvalidException("Bad section entry: " + name);
  811. }
  812. break;
  813. }
  814. if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c)
  815. name.append((char) c);
  816. else
  817. throw new ConfigInvalidException("Bad section entry: " + name);
  818. }
  819. return name.toString();
  820. }
  821. private static String readKeyName(final StringReader in)
  822. throws ConfigInvalidException {
  823. final StringBuffer name = new StringBuffer();
  824. for (;;) {
  825. int c = in.read();
  826. if (c < 0)
  827. throw new ConfigInvalidException("Unexpected end of config file");
  828. if ('=' == c)
  829. break;
  830. if (' ' == c || '\t' == c) {
  831. for (;;) {
  832. c = in.read();
  833. if (c < 0)
  834. throw new ConfigInvalidException("Unexpected end of config file");
  835. if ('=' == c)
  836. break;
  837. if (';' == c || '#' == c || '\n' == c) {
  838. in.reset();
  839. break;
  840. }
  841. if (' ' == c || '\t' == c)
  842. continue; // Skipped...
  843. throw new ConfigInvalidException("Bad entry delimiter");
  844. }
  845. break;
  846. }
  847. if (Character.isLetterOrDigit((char) c) || c == '-') {
  848. // From the git-config man page:
  849. // The variable names are case-insensitive and only
  850. // alphanumeric characters and - are allowed.
  851. name.append((char) c);
  852. } else if ('\n' == c) {
  853. in.reset();
  854. name.append((char) c);
  855. break;
  856. } else
  857. throw new ConfigInvalidException("Bad entry name: " + name);
  858. }
  859. return name.toString();
  860. }
  861. private static String readValue(final StringReader in, boolean quote,
  862. final int eol) throws ConfigInvalidException {
  863. final StringBuffer value = new StringBuffer();
  864. boolean space = false;
  865. for (;;) {
  866. int c = in.read();
  867. if (c < 0) {
  868. if (value.length() == 0)
  869. throw new ConfigInvalidException("Unexpected end of config file");
  870. break;
  871. }
  872. if ('\n' == c) {
  873. if (quote)
  874. throw new ConfigInvalidException("Newline in quotes not allowed");
  875. in.reset();
  876. break;
  877. }
  878. if (eol == c)
  879. break;
  880. if (!quote) {
  881. if (Character.isWhitespace((char) c)) {
  882. space = true;
  883. continue;
  884. }
  885. if (';' == c || '#' == c) {
  886. in.reset();
  887. break;
  888. }
  889. }
  890. if (space) {
  891. if (value.length() > 0)
  892. value.append(' ');
  893. space = false;
  894. }
  895. if ('\\' == c) {
  896. c = in.read();
  897. switch (c) {
  898. case -1:
  899. throw new ConfigInvalidException("End of file in escape");
  900. case '\n':
  901. continue;
  902. case 't':
  903. value.append('\t');
  904. continue;
  905. case 'b':
  906. value.append('\b');
  907. continue;
  908. case 'n':
  909. value.append('\n');
  910. continue;
  911. case '\\':
  912. value.append('\\');
  913. continue;
  914. case '"':
  915. value.append('"');
  916. continue;
  917. default:
  918. throw new ConfigInvalidException("Bad escape: " + ((char) c));
  919. }
  920. }
  921. if ('"' == c) {
  922. quote = !quote;
  923. continue;
  924. }
  925. value.append((char) c);
  926. }
  927. return value.length() > 0 ? value.toString() : null;
  928. }
  929. /**
  930. * Parses a section of the configuration into an application model object.
  931. * <p>
  932. * Instances must implement hashCode and equals such that model objects can
  933. * be cached by using the {@code SectionParser} as a key of a HashMap.
  934. * <p>
  935. * As the {@code SectionParser} itself is used as the key of the internal
  936. * HashMap applications should be careful to ensure the SectionParser key
  937. * does not retain unnecessary application state which may cause memory to
  938. * be held longer than expected.
  939. *
  940. * @param <T>
  941. * type of the application model created by the parser.
  942. */
  943. public static interface SectionParser<T> {
  944. /**
  945. * Create a model object from a configuration.
  946. *
  947. * @param cfg
  948. * the configuration to read values from.
  949. * @return the application model instance.
  950. */
  951. T parse(Config cfg);
  952. }
  953. private static class SubsectionNames implements SectionParser<Set<String>> {
  954. private final String section;
  955. SubsectionNames(final String sectionName) {
  956. section = sectionName;
  957. }
  958. public int hashCode() {
  959. return section.hashCode();
  960. }
  961. public boolean equals(Object other) {
  962. if (other instanceof SubsectionNames) {
  963. return section.equals(((SubsectionNames) other).section);
  964. }
  965. return false;
  966. }
  967. public Set<String> parse(Config cfg) {
  968. final Set<String> result = new HashSet<String>();
  969. while (cfg != null) {
  970. for (final Entry e : cfg.state.get().entryList) {
  971. if (e.subsection != null && e.name == null
  972. && StringUtils.equalsIgnoreCase(section, e.section))
  973. result.add(e.subsection);
  974. }
  975. cfg = cfg.baseConfig;
  976. }
  977. return Collections.unmodifiableSet(result);
  978. }
  979. }
  980. private static class State {
  981. final List<Entry> entryList;
  982. final Map<Object, Object> cache;
  983. final State baseState;
  984. State(List<Entry> entries, State base) {
  985. entryList = entries;
  986. cache = new ConcurrentHashMap<Object, Object>(16, 0.75f, 1);
  987. baseState = base;
  988. }
  989. }
  990. /**
  991. * The configuration file entry
  992. */
  993. private static class Entry {
  994. /**
  995. * The text content before entry
  996. */
  997. String prefix;
  998. /**
  999. * The section name for the entry
  1000. */
  1001. String section;
  1002. /**
  1003. * Subsection name
  1004. */
  1005. String subsection;
  1006. /**
  1007. * The key name
  1008. */
  1009. String name;
  1010. /**
  1011. * The value
  1012. */
  1013. String value;
  1014. /**
  1015. * The text content after entry
  1016. */
  1017. String suffix;
  1018. Entry forValue(final String newValue) {
  1019. final Entry e = new Entry();
  1020. e.prefix = prefix;
  1021. e.section = section;
  1022. e.subsection = subsection;
  1023. e.name = name;
  1024. e.value = newValue;
  1025. e.suffix = suffix;
  1026. return e;
  1027. }
  1028. boolean match(final String aSection, final String aSubsection,
  1029. final String aKey) {
  1030. return eqIgnoreCase(section, aSection)
  1031. && eqSameCase(subsection, aSubsection)
  1032. && eqIgnoreCase(name, aKey);
  1033. }
  1034. boolean match(final String aSection, final String aSubsection) {
  1035. return eqIgnoreCase(section, aSection)
  1036. && eqSameCase(subsection, aSubsection);
  1037. }
  1038. private static boolean eqIgnoreCase(final String a, final String b) {
  1039. if (a == null && b == null)
  1040. return true;
  1041. if (a == null || b == null)
  1042. return false;
  1043. return StringUtils.equalsIgnoreCase(a, b);
  1044. }
  1045. private static boolean eqSameCase(final String a, final String b) {
  1046. if (a == null && b == null)
  1047. return true;
  1048. if (a == null || b == null)
  1049. return false;
  1050. return a.equals(b);
  1051. }
  1052. }
  1053. private static class StringReader {
  1054. private final char[] buf;
  1055. private int pos;
  1056. StringReader(final String in) {
  1057. buf = in.toCharArray();
  1058. }
  1059. int read() {
  1060. try {
  1061. return buf[pos++];
  1062. } catch (ArrayIndexOutOfBoundsException e) {
  1063. pos = buf.length;
  1064. return -1;
  1065. }
  1066. }
  1067. void reset() {
  1068. pos--;
  1069. }
  1070. }
  1071. }