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 42KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508
  1. /*
  2. * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
  3. * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
  4. * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  5. * Copyright (C) 2008-2010, Google Inc.
  6. * Copyright (C) 2009, Google, Inc.
  7. * Copyright (C) 2009, JetBrains s.r.o.
  8. * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  9. * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
  10. * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com> and others
  11. *
  12. * This program and the accompanying materials are made available under the
  13. * terms of the Eclipse Distribution License v. 1.0 which is available at
  14. * https://www.eclipse.org/org/documents/edl-v10.php.
  15. *
  16. * SPDX-License-Identifier: BSD-3-Clause
  17. */
  18. package org.eclipse.jgit.lib;
  19. import static java.nio.charset.StandardCharsets.UTF_8;
  20. import java.text.MessageFormat;
  21. import java.util.ArrayList;
  22. import java.util.Collections;
  23. import java.util.List;
  24. import java.util.Locale;
  25. import java.util.Set;
  26. import java.util.concurrent.TimeUnit;
  27. import java.util.concurrent.atomic.AtomicReference;
  28. import org.eclipse.jgit.errors.ConfigInvalidException;
  29. import org.eclipse.jgit.events.ConfigChangedEvent;
  30. import org.eclipse.jgit.events.ConfigChangedListener;
  31. import org.eclipse.jgit.events.ListenerHandle;
  32. import org.eclipse.jgit.events.ListenerList;
  33. import org.eclipse.jgit.internal.JGitText;
  34. import org.eclipse.jgit.transport.RefSpec;
  35. import org.eclipse.jgit.util.RawParseUtils;
  36. /**
  37. * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.
  38. */
  39. public class Config {
  40. private static final String[] EMPTY_STRING_ARRAY = {};
  41. static final long KiB = 1024;
  42. static final long MiB = 1024 * KiB;
  43. static final long GiB = 1024 * MiB;
  44. private static final int MAX_DEPTH = 10;
  45. private static final TypedConfigGetter DEFAULT_GETTER = new DefaultTypedConfigGetter();
  46. private static TypedConfigGetter typedGetter = DEFAULT_GETTER;
  47. /** the change listeners */
  48. private final ListenerList listeners = new ListenerList();
  49. /**
  50. * Immutable current state of the configuration data.
  51. * <p>
  52. * This state is copy-on-write. It should always contain an immutable list
  53. * of the configuration keys/values.
  54. */
  55. private final AtomicReference<ConfigSnapshot> state;
  56. private final Config baseConfig;
  57. /**
  58. * Magic value indicating a missing entry.
  59. * <p>
  60. * This value is tested for reference equality in some contexts, so we
  61. * must ensure it is a special copy of the empty string. It also must
  62. * be treated like the empty string.
  63. */
  64. private static final String MISSING_ENTRY = new String();
  65. /**
  66. * Create a configuration with no default fallback.
  67. */
  68. public Config() {
  69. this(null);
  70. }
  71. /**
  72. * Create an empty configuration with a fallback for missing keys.
  73. *
  74. * @param defaultConfig
  75. * the base configuration to be consulted when a key is missing
  76. * from this configuration instance.
  77. */
  78. public Config(Config defaultConfig) {
  79. baseConfig = defaultConfig;
  80. state = new AtomicReference<>(newState());
  81. }
  82. /**
  83. * Retrieves this config's base config.
  84. *
  85. * @return the base configuration of this config.
  86. *
  87. * @since 5.5.2
  88. */
  89. public Config getBaseConfig() {
  90. return baseConfig;
  91. }
  92. /**
  93. * Check if a given string is the "missing" value.
  94. *
  95. * @param value
  96. * string to be checked.
  97. * @return true if the given string is the "missing" value.
  98. * @since 5.4
  99. */
  100. @SuppressWarnings({ "ReferenceEquality", "StringEquality" })
  101. public static boolean isMissing(String value) {
  102. return value == MISSING_ENTRY;
  103. }
  104. /**
  105. * Globally sets a {@link org.eclipse.jgit.lib.TypedConfigGetter} that is
  106. * subsequently used to read typed values from all git configs.
  107. *
  108. * @param getter
  109. * to use; if {@code null} use the default getter.
  110. * @since 4.9
  111. */
  112. public static void setTypedConfigGetter(TypedConfigGetter getter) {
  113. typedGetter = getter == null ? DEFAULT_GETTER : getter;
  114. }
  115. /**
  116. * Escape the value before saving
  117. *
  118. * @param x
  119. * the value to escape
  120. * @return the escaped value
  121. */
  122. static String escapeValue(String x) {
  123. if (x.isEmpty()) {
  124. return ""; //$NON-NLS-1$
  125. }
  126. boolean needQuote = x.charAt(0) == ' ' || x.charAt(x.length() - 1) == ' ';
  127. StringBuilder r = new StringBuilder(x.length());
  128. for (int k = 0; k < x.length(); k++) {
  129. char c = x.charAt(k);
  130. // git-config(1) lists the limited set of supported escape sequences, but
  131. // the documentation is otherwise not especially normative. In particular,
  132. // which ones of these produce and/or require escaping and/or quoting
  133. // around them is not documented and was discovered by trial and error.
  134. // In summary:
  135. //
  136. // * Quotes are only required if there is leading/trailing whitespace or a
  137. // comment character.
  138. // * Bytes that have a supported escape sequence are escaped, except for
  139. // \b for some reason which isn't.
  140. // * Needing an escape sequence is not sufficient reason to quote the
  141. // value.
  142. switch (c) {
  143. case '\0':
  144. // Unix command line calling convention cannot pass a '\0' as an
  145. // argument, so there is no equivalent way in C git to store a null byte
  146. // in a config value.
  147. throw new IllegalArgumentException(
  148. JGitText.get().configValueContainsNullByte);
  149. case '\n':
  150. r.append('\\').append('n');
  151. break;
  152. case '\t':
  153. r.append('\\').append('t');
  154. break;
  155. case '\b':
  156. // Doesn't match `git config foo.bar $'x\by'`, which doesn't escape the
  157. // \x08, but since both escaped and unescaped forms are readable, we'll
  158. // prefer internal consistency here.
  159. r.append('\\').append('b');
  160. break;
  161. case '\\':
  162. r.append('\\').append('\\');
  163. break;
  164. case '"':
  165. r.append('\\').append('"');
  166. break;
  167. case '#':
  168. case ';':
  169. needQuote = true;
  170. r.append(c);
  171. break;
  172. default:
  173. r.append(c);
  174. break;
  175. }
  176. }
  177. return needQuote ? '"' + r.toString() + '"' : r.toString();
  178. }
  179. static String escapeSubsection(String x) {
  180. if (x.isEmpty()) {
  181. return "\"\""; //$NON-NLS-1$
  182. }
  183. StringBuilder r = new StringBuilder(x.length() + 2).append('"');
  184. for (int k = 0; k < x.length(); k++) {
  185. char c = x.charAt(k);
  186. // git-config(1) lists the limited set of supported escape sequences
  187. // (which is even more limited for subsection names than for values).
  188. switch (c) {
  189. case '\0':
  190. throw new IllegalArgumentException(
  191. JGitText.get().configSubsectionContainsNullByte);
  192. case '\n':
  193. throw new IllegalArgumentException(
  194. JGitText.get().configSubsectionContainsNewline);
  195. case '\\':
  196. case '"':
  197. r.append('\\').append(c);
  198. break;
  199. default:
  200. r.append(c);
  201. break;
  202. }
  203. }
  204. return r.append('"').toString();
  205. }
  206. /**
  207. * Obtain an integer value from the configuration.
  208. *
  209. * @param section
  210. * section the key is grouped within.
  211. * @param name
  212. * name of the key to get.
  213. * @param defaultValue
  214. * default value to return if no value was present.
  215. * @return an integer value from the configuration, or defaultValue.
  216. */
  217. public int getInt(final String section, final String name,
  218. final int defaultValue) {
  219. return typedGetter.getInt(this, section, null, name, defaultValue);
  220. }
  221. /**
  222. * Obtain an integer value from the configuration.
  223. *
  224. * @param section
  225. * section the key is grouped within.
  226. * @param subsection
  227. * subsection name, such a remote or branch name.
  228. * @param name
  229. * name of the key to get.
  230. * @param defaultValue
  231. * default value to return if no value was present.
  232. * @return an integer value from the configuration, or defaultValue.
  233. */
  234. public int getInt(final String section, String subsection,
  235. final String name, final int defaultValue) {
  236. return typedGetter.getInt(this, section, subsection, name,
  237. defaultValue);
  238. }
  239. /**
  240. * Obtain an integer value from the configuration.
  241. *
  242. * @param section
  243. * section the key is grouped within.
  244. * @param name
  245. * name of the key to get.
  246. * @param defaultValue
  247. * default value to return if no value was present.
  248. * @return an integer value from the configuration, or defaultValue.
  249. */
  250. public long getLong(String section, String name, long defaultValue) {
  251. return typedGetter.getLong(this, section, null, name, defaultValue);
  252. }
  253. /**
  254. * Obtain an integer value from the configuration.
  255. *
  256. * @param section
  257. * section the key is grouped within.
  258. * @param subsection
  259. * subsection name, such a remote or branch name.
  260. * @param name
  261. * name of the key to get.
  262. * @param defaultValue
  263. * default value to return if no value was present.
  264. * @return an integer value from the configuration, or defaultValue.
  265. */
  266. public long getLong(final String section, String subsection,
  267. final String name, final long defaultValue) {
  268. return typedGetter.getLong(this, section, subsection, name,
  269. defaultValue);
  270. }
  271. /**
  272. * Get a boolean value from the git config
  273. *
  274. * @param section
  275. * section the key is grouped within.
  276. * @param name
  277. * name of the key to get.
  278. * @param defaultValue
  279. * default value to return if no value was present.
  280. * @return true if any value or defaultValue is true, false for missing or
  281. * explicit false
  282. */
  283. public boolean getBoolean(final String section, final String name,
  284. final boolean defaultValue) {
  285. return typedGetter.getBoolean(this, section, null, name, defaultValue);
  286. }
  287. /**
  288. * Get a boolean value from the git config
  289. *
  290. * @param section
  291. * section the key is grouped within.
  292. * @param subsection
  293. * subsection name, such a remote or branch name.
  294. * @param name
  295. * name of the key to get.
  296. * @param defaultValue
  297. * default value to return if no value was present.
  298. * @return true if any value or defaultValue is true, false for missing or
  299. * explicit false
  300. */
  301. public boolean getBoolean(final String section, String subsection,
  302. final String name, final boolean defaultValue) {
  303. return typedGetter.getBoolean(this, section, subsection, name,
  304. defaultValue);
  305. }
  306. /**
  307. * Parse an enumeration from the configuration.
  308. *
  309. * @param section
  310. * section the key is grouped within.
  311. * @param subsection
  312. * subsection name, such a remote or branch name.
  313. * @param name
  314. * name of the key to get.
  315. * @param defaultValue
  316. * default value to return if no value was present.
  317. * @return the selected enumeration value, or {@code defaultValue}.
  318. */
  319. public <T extends Enum<?>> T getEnum(final String section,
  320. final String subsection, final String name, final T defaultValue) {
  321. final T[] all = allValuesOf(defaultValue);
  322. return typedGetter.getEnum(this, all, section, subsection, name,
  323. defaultValue);
  324. }
  325. @SuppressWarnings("unchecked")
  326. private static <T> T[] allValuesOf(T value) {
  327. try {
  328. return (T[]) value.getClass().getMethod("values").invoke(null); //$NON-NLS-1$
  329. } catch (Exception err) {
  330. String typeName = value.getClass().getName();
  331. String msg = MessageFormat.format(
  332. JGitText.get().enumValuesNotAvailable, typeName);
  333. throw new IllegalArgumentException(msg, err);
  334. }
  335. }
  336. /**
  337. * Parse an enumeration from the configuration.
  338. *
  339. * @param all
  340. * all possible values in the enumeration which should be
  341. * recognized. Typically {@code EnumType.values()}.
  342. * @param section
  343. * section the key is grouped within.
  344. * @param subsection
  345. * subsection name, such a remote or branch name.
  346. * @param name
  347. * name of the key to get.
  348. * @param defaultValue
  349. * default value to return if no value was present.
  350. * @return the selected enumeration value, or {@code defaultValue}.
  351. */
  352. public <T extends Enum<?>> T getEnum(final T[] all, final String section,
  353. final String subsection, final String name, final T defaultValue) {
  354. return typedGetter.getEnum(this, all, section, subsection, name,
  355. defaultValue);
  356. }
  357. /**
  358. * Get string value or null if not found.
  359. *
  360. * @param section
  361. * the section
  362. * @param subsection
  363. * the subsection for the value
  364. * @param name
  365. * the key name
  366. * @return a String value from the config, <code>null</code> if not found
  367. */
  368. public String getString(final String section, String subsection,
  369. final String name) {
  370. return getRawString(section, subsection, name);
  371. }
  372. /**
  373. * Get a list of string values
  374. * <p>
  375. * If this instance was created with a base, the base's values are returned
  376. * first (if any).
  377. *
  378. * @param section
  379. * the section
  380. * @param subsection
  381. * the subsection for the value
  382. * @param name
  383. * the key name
  384. * @return array of zero or more values from the configuration.
  385. */
  386. public String[] getStringList(final String section, String subsection,
  387. final String name) {
  388. String[] base;
  389. if (baseConfig != null)
  390. base = baseConfig.getStringList(section, subsection, name);
  391. else
  392. base = EMPTY_STRING_ARRAY;
  393. String[] self = getRawStringList(section, subsection, name);
  394. if (self == null)
  395. return base;
  396. if (base.length == 0)
  397. return self;
  398. String[] res = new String[base.length + self.length];
  399. int n = base.length;
  400. System.arraycopy(base, 0, res, 0, n);
  401. System.arraycopy(self, 0, res, n, self.length);
  402. return res;
  403. }
  404. /**
  405. * Parse a numerical time unit, such as "1 minute", from the configuration.
  406. *
  407. * @param section
  408. * section the key is in.
  409. * @param subsection
  410. * subsection the key is in, or null if not in a subsection.
  411. * @param name
  412. * the key name.
  413. * @param defaultValue
  414. * default value to return if no value was present.
  415. * @param wantUnit
  416. * the units of {@code defaultValue} and the return value, as
  417. * well as the units to assume if the value does not contain an
  418. * indication of the units.
  419. * @return the value, or {@code defaultValue} if not set, expressed in
  420. * {@code units}.
  421. * @since 4.5
  422. */
  423. public long getTimeUnit(String section, String subsection, String name,
  424. long defaultValue, TimeUnit wantUnit) {
  425. return typedGetter.getTimeUnit(this, section, subsection, name,
  426. defaultValue, wantUnit);
  427. }
  428. /**
  429. * Parse a list of {@link org.eclipse.jgit.transport.RefSpec}s from the
  430. * configuration.
  431. *
  432. * @param section
  433. * section the key is in.
  434. * @param subsection
  435. * subsection the key is in, or null if not in a subsection.
  436. * @param name
  437. * the key name.
  438. * @return a possibly empty list of
  439. * {@link org.eclipse.jgit.transport.RefSpec}s
  440. * @since 4.9
  441. */
  442. public List<RefSpec> getRefSpecs(String section, String subsection,
  443. String name) {
  444. return typedGetter.getRefSpecs(this, section, subsection, name);
  445. }
  446. /**
  447. * Get set of all subsections of specified section within this configuration
  448. * and its base configuration
  449. *
  450. * @param section
  451. * section to search for.
  452. * @return set of all subsections of specified section within this
  453. * configuration and its base configuration; may be empty if no
  454. * subsection exists. The set's iterator returns sections in the
  455. * order they are declared by the configuration starting from this
  456. * instance and progressing through the base.
  457. */
  458. public Set<String> getSubsections(String section) {
  459. return getState().getSubsections(section);
  460. }
  461. /**
  462. * Get the sections defined in this {@link org.eclipse.jgit.lib.Config}.
  463. *
  464. * @return the sections defined in this {@link org.eclipse.jgit.lib.Config}.
  465. * The set's iterator returns sections in the order they are
  466. * declared by the configuration starting from this instance and
  467. * progressing through the base.
  468. */
  469. public Set<String> getSections() {
  470. return getState().getSections();
  471. }
  472. /**
  473. * Get the list of names defined for this section
  474. *
  475. * @param section
  476. * the section
  477. * @return the list of names defined for this section
  478. */
  479. public Set<String> getNames(String section) {
  480. return getNames(section, null);
  481. }
  482. /**
  483. * Get the list of names defined for this subsection
  484. *
  485. * @param section
  486. * the section
  487. * @param subsection
  488. * the subsection
  489. * @return the list of names defined for this subsection
  490. */
  491. public Set<String> getNames(String section, String subsection) {
  492. return getState().getNames(section, subsection);
  493. }
  494. /**
  495. * Get the list of names defined for this section
  496. *
  497. * @param section
  498. * the section
  499. * @param recursive
  500. * if {@code true} recursively adds the names defined in all base
  501. * configurations
  502. * @return the list of names defined for this section
  503. * @since 3.2
  504. */
  505. public Set<String> getNames(String section, boolean recursive) {
  506. return getState().getNames(section, null, recursive);
  507. }
  508. /**
  509. * Get the list of names defined for this section
  510. *
  511. * @param section
  512. * the section
  513. * @param subsection
  514. * the subsection
  515. * @param recursive
  516. * if {@code true} recursively adds the names defined in all base
  517. * configurations
  518. * @return the list of names defined for this subsection
  519. * @since 3.2
  520. */
  521. public Set<String> getNames(String section, String subsection,
  522. boolean recursive) {
  523. return getState().getNames(section, subsection, recursive);
  524. }
  525. /**
  526. * Obtain a handle to a parsed set of configuration values.
  527. *
  528. * @param <T>
  529. * type of configuration model to return.
  530. * @param parser
  531. * parser which can create the model if it is not already
  532. * available in this configuration file. The parser is also used
  533. * as the key into a cache and must obey the hashCode and equals
  534. * contract in order to reuse a parsed model.
  535. * @return the parsed object instance, which is cached inside this config.
  536. */
  537. @SuppressWarnings("unchecked")
  538. public <T> T get(SectionParser<T> parser) {
  539. final ConfigSnapshot myState = getState();
  540. T obj = (T) myState.cache.get(parser);
  541. if (obj == null) {
  542. obj = parser.parse(this);
  543. myState.cache.put(parser, obj);
  544. }
  545. return obj;
  546. }
  547. /**
  548. * Remove a cached configuration object.
  549. * <p>
  550. * If the associated configuration object has not yet been cached, this
  551. * method has no effect.
  552. *
  553. * @param parser
  554. * parser used to obtain the configuration object.
  555. * @see #get(SectionParser)
  556. */
  557. public void uncache(SectionParser<?> parser) {
  558. state.get().cache.remove(parser);
  559. }
  560. /**
  561. * Adds a listener to be notified about changes.
  562. * <p>
  563. * Clients are supposed to remove the listeners after they are done with
  564. * them using the {@link org.eclipse.jgit.events.ListenerHandle#remove()}
  565. * method
  566. *
  567. * @param listener
  568. * the listener
  569. * @return the handle to the registered listener
  570. */
  571. public ListenerHandle addChangeListener(ConfigChangedListener listener) {
  572. return listeners.addConfigChangedListener(listener);
  573. }
  574. /**
  575. * Determine whether to issue change events for transient changes.
  576. * <p>
  577. * If <code>true</code> is returned (which is the default behavior),
  578. * {@link #fireConfigChangedEvent()} will be called upon each change.
  579. * <p>
  580. * Subclasses that override this to return <code>false</code> are
  581. * responsible for issuing {@link #fireConfigChangedEvent()} calls
  582. * themselves.
  583. *
  584. * @return <code></code>
  585. */
  586. protected boolean notifyUponTransientChanges() {
  587. return true;
  588. }
  589. /**
  590. * Notifies the listeners
  591. */
  592. protected void fireConfigChangedEvent() {
  593. listeners.dispatch(new ConfigChangedEvent());
  594. }
  595. String getRawString(final String section, final String subsection,
  596. final String name) {
  597. String[] lst = getRawStringList(section, subsection, name);
  598. if (lst != null) {
  599. return lst[lst.length - 1];
  600. } else if (baseConfig != null) {
  601. return baseConfig.getRawString(section, subsection, name);
  602. } else {
  603. return null;
  604. }
  605. }
  606. private String[] getRawStringList(String section, String subsection,
  607. String name) {
  608. return state.get().get(section, subsection, name);
  609. }
  610. private ConfigSnapshot getState() {
  611. ConfigSnapshot cur, upd;
  612. do {
  613. cur = state.get();
  614. final ConfigSnapshot base = getBaseState();
  615. if (cur.baseState == base)
  616. return cur;
  617. upd = new ConfigSnapshot(cur.entryList, base);
  618. } while (!state.compareAndSet(cur, upd));
  619. return upd;
  620. }
  621. private ConfigSnapshot getBaseState() {
  622. return baseConfig != null ? baseConfig.getState() : null;
  623. }
  624. /**
  625. * Add or modify a configuration value. The parameters will result in a
  626. * configuration entry like this.
  627. *
  628. * <pre>
  629. * [section &quot;subsection&quot;]
  630. * name = value
  631. * </pre>
  632. *
  633. * @param section
  634. * section name, e.g "branch"
  635. * @param subsection
  636. * optional subsection value, e.g. a branch name
  637. * @param name
  638. * parameter name, e.g. "filemode"
  639. * @param value
  640. * parameter value
  641. */
  642. public void setInt(final String section, final String subsection,
  643. final String name, final int value) {
  644. setLong(section, subsection, name, value);
  645. }
  646. /**
  647. * Add or modify a configuration value. The parameters will result in a
  648. * configuration entry like this.
  649. *
  650. * <pre>
  651. * [section &quot;subsection&quot;]
  652. * name = value
  653. * </pre>
  654. *
  655. * @param section
  656. * section name, e.g "branch"
  657. * @param subsection
  658. * optional subsection value, e.g. a branch name
  659. * @param name
  660. * parameter name, e.g. "filemode"
  661. * @param value
  662. * parameter value
  663. */
  664. public void setLong(final String section, final String subsection,
  665. final String name, final long value) {
  666. final String s;
  667. if (value >= GiB && (value % GiB) == 0)
  668. s = String.valueOf(value / GiB) + "g"; //$NON-NLS-1$
  669. else if (value >= MiB && (value % MiB) == 0)
  670. s = String.valueOf(value / MiB) + "m"; //$NON-NLS-1$
  671. else if (value >= KiB && (value % KiB) == 0)
  672. s = String.valueOf(value / KiB) + "k"; //$NON-NLS-1$
  673. else
  674. s = String.valueOf(value);
  675. setString(section, subsection, name, s);
  676. }
  677. /**
  678. * Add or modify a configuration value. The parameters will result in a
  679. * configuration entry like this.
  680. *
  681. * <pre>
  682. * [section &quot;subsection&quot;]
  683. * name = value
  684. * </pre>
  685. *
  686. * @param section
  687. * section name, e.g "branch"
  688. * @param subsection
  689. * optional subsection value, e.g. a branch name
  690. * @param name
  691. * parameter name, e.g. "filemode"
  692. * @param value
  693. * parameter value
  694. */
  695. public void setBoolean(final String section, final String subsection,
  696. final String name, final boolean value) {
  697. setString(section, subsection, name, value ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
  698. }
  699. /**
  700. * Add or modify a configuration value. The parameters will result in a
  701. * configuration entry like this.
  702. *
  703. * <pre>
  704. * [section &quot;subsection&quot;]
  705. * name = value
  706. * </pre>
  707. *
  708. * @param section
  709. * section name, e.g "branch"
  710. * @param subsection
  711. * optional subsection value, e.g. a branch name
  712. * @param name
  713. * parameter name, e.g. "filemode"
  714. * @param value
  715. * parameter value
  716. */
  717. public <T extends Enum<?>> void setEnum(final String section,
  718. final String subsection, final String name, final T value) {
  719. String n;
  720. if (value instanceof ConfigEnum)
  721. n = ((ConfigEnum) value).toConfigValue();
  722. else
  723. n = value.name().toLowerCase(Locale.ROOT).replace('_', ' ');
  724. setString(section, subsection, name, n);
  725. }
  726. /**
  727. * Add or modify a configuration value. The parameters will result in a
  728. * configuration entry like this.
  729. *
  730. * <pre>
  731. * [section &quot;subsection&quot;]
  732. * name = value
  733. * </pre>
  734. *
  735. * @param section
  736. * section name, e.g "branch"
  737. * @param subsection
  738. * optional subsection value, e.g. a branch name
  739. * @param name
  740. * parameter name, e.g. "filemode"
  741. * @param value
  742. * parameter value, e.g. "true"
  743. */
  744. public void setString(final String section, final String subsection,
  745. final String name, final String value) {
  746. setStringList(section, subsection, name, Collections
  747. .singletonList(value));
  748. }
  749. /**
  750. * Remove a configuration value.
  751. *
  752. * @param section
  753. * section name, e.g "branch"
  754. * @param subsection
  755. * optional subsection value, e.g. a branch name
  756. * @param name
  757. * parameter name, e.g. "filemode"
  758. */
  759. public void unset(final String section, final String subsection,
  760. final String name) {
  761. setStringList(section, subsection, name, Collections
  762. .<String> emptyList());
  763. }
  764. /**
  765. * Remove all configuration values under a single section.
  766. *
  767. * @param section
  768. * section name, e.g "branch"
  769. * @param subsection
  770. * optional subsection value, e.g. a branch name
  771. */
  772. public void unsetSection(String section, String subsection) {
  773. ConfigSnapshot src, res;
  774. do {
  775. src = state.get();
  776. res = unsetSection(src, section, subsection);
  777. } while (!state.compareAndSet(src, res));
  778. }
  779. private ConfigSnapshot unsetSection(final ConfigSnapshot srcState,
  780. final String section,
  781. final String subsection) {
  782. final int max = srcState.entryList.size();
  783. final ArrayList<ConfigLine> r = new ArrayList<>(max);
  784. boolean lastWasMatch = false;
  785. for (ConfigLine e : srcState.entryList) {
  786. if (e.includedFrom == null && e.match(section, subsection)) {
  787. // Skip this record, it's for the section we are removing.
  788. lastWasMatch = true;
  789. continue;
  790. }
  791. if (lastWasMatch && e.section == null && e.subsection == null)
  792. continue; // skip this padding line in the section.
  793. r.add(e);
  794. }
  795. return newState(r);
  796. }
  797. /**
  798. * Set a configuration value.
  799. *
  800. * <pre>
  801. * [section &quot;subsection&quot;]
  802. * name = value1
  803. * name = value2
  804. * </pre>
  805. *
  806. * @param section
  807. * section name, e.g "branch"
  808. * @param subsection
  809. * optional subsection value, e.g. a branch name
  810. * @param name
  811. * parameter name, e.g. "filemode"
  812. * @param values
  813. * list of zero or more values for this key.
  814. */
  815. public void setStringList(final String section, final String subsection,
  816. final String name, final List<String> values) {
  817. ConfigSnapshot src, res;
  818. do {
  819. src = state.get();
  820. res = replaceStringList(src, section, subsection, name, values);
  821. } while (!state.compareAndSet(src, res));
  822. if (notifyUponTransientChanges())
  823. fireConfigChangedEvent();
  824. }
  825. private ConfigSnapshot replaceStringList(final ConfigSnapshot srcState,
  826. final String section, final String subsection, final String name,
  827. final List<String> values) {
  828. final List<ConfigLine> entries = copy(srcState, values);
  829. int entryIndex = 0;
  830. int valueIndex = 0;
  831. int insertPosition = -1;
  832. // Reset the first n Entry objects that match this input name.
  833. //
  834. while (entryIndex < entries.size() && valueIndex < values.size()) {
  835. final ConfigLine e = entries.get(entryIndex);
  836. if (e.includedFrom == null && e.match(section, subsection, name)) {
  837. entries.set(entryIndex, e.forValue(values.get(valueIndex++)));
  838. insertPosition = entryIndex + 1;
  839. }
  840. entryIndex++;
  841. }
  842. // Remove any extra Entry objects that we no longer need.
  843. //
  844. if (valueIndex == values.size() && entryIndex < entries.size()) {
  845. while (entryIndex < entries.size()) {
  846. final ConfigLine e = entries.get(entryIndex++);
  847. if (e.includedFrom == null
  848. && e.match(section, subsection, name))
  849. entries.remove(--entryIndex);
  850. }
  851. }
  852. // Insert new Entry objects for additional/new values.
  853. //
  854. if (valueIndex < values.size() && entryIndex == entries.size()) {
  855. if (insertPosition < 0) {
  856. // We didn't find a matching key above, but maybe there
  857. // is already a section available that matches. Insert
  858. // after the last key of that section.
  859. //
  860. insertPosition = findSectionEnd(entries, section, subsection,
  861. true);
  862. }
  863. if (insertPosition < 0) {
  864. // We didn't find any matching section header for this key,
  865. // so we must create a new section header at the end.
  866. //
  867. final ConfigLine e = new ConfigLine();
  868. e.section = section;
  869. e.subsection = subsection;
  870. entries.add(e);
  871. insertPosition = entries.size();
  872. }
  873. while (valueIndex < values.size()) {
  874. final ConfigLine e = new ConfigLine();
  875. e.section = section;
  876. e.subsection = subsection;
  877. e.name = name;
  878. e.value = values.get(valueIndex++);
  879. entries.add(insertPosition++, e);
  880. }
  881. }
  882. return newState(entries);
  883. }
  884. private static List<ConfigLine> copy(final ConfigSnapshot src,
  885. final List<String> values) {
  886. // At worst we need to insert 1 line for each value, plus 1 line
  887. // for a new section header. Assume that and allocate the space.
  888. //
  889. final int max = src.entryList.size() + values.size() + 1;
  890. final ArrayList<ConfigLine> r = new ArrayList<>(max);
  891. r.addAll(src.entryList);
  892. return r;
  893. }
  894. private static int findSectionEnd(final List<ConfigLine> entries,
  895. final String section, final String subsection,
  896. boolean skipIncludedLines) {
  897. for (int i = 0; i < entries.size(); i++) {
  898. ConfigLine e = entries.get(i);
  899. if (e.includedFrom != null && skipIncludedLines) {
  900. continue;
  901. }
  902. if (e.match(section, subsection, null)) {
  903. i++;
  904. while (i < entries.size()) {
  905. e = entries.get(i);
  906. if (e.match(section, subsection, e.name))
  907. i++;
  908. else
  909. break;
  910. }
  911. return i;
  912. }
  913. }
  914. return -1;
  915. }
  916. /**
  917. * Get this configuration, formatted as a Git style text file.
  918. *
  919. * @return this configuration, formatted as a Git style text file.
  920. */
  921. public String toText() {
  922. final StringBuilder out = new StringBuilder();
  923. for (ConfigLine e : state.get().entryList) {
  924. if (e.includedFrom != null)
  925. continue;
  926. if (e.prefix != null)
  927. out.append(e.prefix);
  928. if (e.section != null && e.name == null) {
  929. out.append('[');
  930. out.append(e.section);
  931. if (e.subsection != null) {
  932. out.append(' ');
  933. String escaped = escapeValue(e.subsection);
  934. // make sure to avoid double quotes here
  935. boolean quoted = escaped.startsWith("\"") //$NON-NLS-1$
  936. && escaped.endsWith("\""); //$NON-NLS-1$
  937. if (!quoted)
  938. out.append('"');
  939. out.append(escaped);
  940. if (!quoted)
  941. out.append('"');
  942. }
  943. out.append(']');
  944. } else if (e.section != null && e.name != null) {
  945. if (e.prefix == null || "".equals(e.prefix)) //$NON-NLS-1$
  946. out.append('\t');
  947. out.append(e.name);
  948. if (!isMissing(e.value)) {
  949. out.append(" ="); //$NON-NLS-1$
  950. if (e.value != null) {
  951. out.append(' ');
  952. out.append(escapeValue(e.value));
  953. }
  954. }
  955. if (e.suffix != null)
  956. out.append(' ');
  957. }
  958. if (e.suffix != null)
  959. out.append(e.suffix);
  960. out.append('\n');
  961. }
  962. return out.toString();
  963. }
  964. /**
  965. * Clear this configuration and reset to the contents of the parsed string.
  966. *
  967. * @param text
  968. * Git style text file listing configuration properties.
  969. * @throws org.eclipse.jgit.errors.ConfigInvalidException
  970. * the text supplied is not formatted correctly. No changes were
  971. * made to {@code this}.
  972. */
  973. public void fromText(String text) throws ConfigInvalidException {
  974. state.set(newState(fromTextRecurse(text, 1, null)));
  975. }
  976. private List<ConfigLine> fromTextRecurse(String text, int depth,
  977. String includedFrom) throws ConfigInvalidException {
  978. if (depth > MAX_DEPTH) {
  979. throw new ConfigInvalidException(
  980. JGitText.get().tooManyIncludeRecursions);
  981. }
  982. final List<ConfigLine> newEntries = new ArrayList<>();
  983. final StringReader in = new StringReader(text);
  984. ConfigLine last = null;
  985. ConfigLine e = new ConfigLine();
  986. e.includedFrom = includedFrom;
  987. for (;;) {
  988. int input = in.read();
  989. if (-1 == input) {
  990. if (e.section != null)
  991. newEntries.add(e);
  992. break;
  993. }
  994. final char c = (char) input;
  995. if ('\n' == c) {
  996. // End of this entry.
  997. newEntries.add(e);
  998. if (e.section != null)
  999. last = e;
  1000. e = new ConfigLine();
  1001. e.includedFrom = includedFrom;
  1002. } else if (e.suffix != null) {
  1003. // Everything up until the end-of-line is in the suffix.
  1004. e.suffix += c;
  1005. } else if (';' == c || '#' == c) {
  1006. // The rest of this line is a comment; put into suffix.
  1007. e.suffix = String.valueOf(c);
  1008. } else if (e.section == null && Character.isWhitespace(c)) {
  1009. // Save the leading whitespace (if any).
  1010. if (e.prefix == null)
  1011. e.prefix = ""; //$NON-NLS-1$
  1012. e.prefix += c;
  1013. } else if ('[' == c) {
  1014. // This is a section header.
  1015. e.section = readSectionName(in);
  1016. input = in.read();
  1017. if ('"' == input) {
  1018. e.subsection = readSubsectionName(in);
  1019. input = in.read();
  1020. }
  1021. if (']' != input)
  1022. throw new ConfigInvalidException(JGitText.get().badGroupHeader);
  1023. e.suffix = ""; //$NON-NLS-1$
  1024. } else if (last != null) {
  1025. // Read a value.
  1026. e.section = last.section;
  1027. e.subsection = last.subsection;
  1028. in.reset();
  1029. e.name = readKeyName(in);
  1030. if (e.name.endsWith("\n")) { //$NON-NLS-1$
  1031. e.name = e.name.substring(0, e.name.length() - 1);
  1032. e.value = MISSING_ENTRY;
  1033. } else
  1034. e.value = readValue(in);
  1035. if (e.section.equalsIgnoreCase("include")) { //$NON-NLS-1$
  1036. addIncludedConfig(newEntries, e, depth);
  1037. }
  1038. } else
  1039. throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile);
  1040. }
  1041. return newEntries;
  1042. }
  1043. /**
  1044. * Read the included config from the specified (possibly) relative path
  1045. *
  1046. * @param relPath
  1047. * possibly relative path to the included config, as specified in
  1048. * this config
  1049. * @return the read bytes, or null if the included config should be ignored
  1050. * @throws org.eclipse.jgit.errors.ConfigInvalidException
  1051. * if something went wrong while reading the config
  1052. * @since 4.10
  1053. */
  1054. protected byte[] readIncludedConfig(String relPath)
  1055. throws ConfigInvalidException {
  1056. return null;
  1057. }
  1058. private void addIncludedConfig(final List<ConfigLine> newEntries,
  1059. ConfigLine line, int depth) throws ConfigInvalidException {
  1060. if (!line.name.equalsIgnoreCase("path") || //$NON-NLS-1$
  1061. line.value == null || line.value.equals(MISSING_ENTRY)) {
  1062. throw new ConfigInvalidException(MessageFormat.format(
  1063. JGitText.get().invalidLineInConfigFileWithParam, line));
  1064. }
  1065. byte[] bytes = readIncludedConfig(line.value);
  1066. if (bytes == null) {
  1067. return;
  1068. }
  1069. String decoded;
  1070. if (isUtf8(bytes)) {
  1071. decoded = RawParseUtils.decode(UTF_8, bytes, 3, bytes.length);
  1072. } else {
  1073. decoded = RawParseUtils.decode(bytes);
  1074. }
  1075. try {
  1076. newEntries.addAll(fromTextRecurse(decoded, depth + 1, line.value));
  1077. } catch (ConfigInvalidException e) {
  1078. throw new ConfigInvalidException(MessageFormat
  1079. .format(JGitText.get().cannotReadFile, line.value), e);
  1080. }
  1081. }
  1082. private ConfigSnapshot newState() {
  1083. return new ConfigSnapshot(Collections.<ConfigLine> emptyList(),
  1084. getBaseState());
  1085. }
  1086. private ConfigSnapshot newState(List<ConfigLine> entries) {
  1087. return new ConfigSnapshot(Collections.unmodifiableList(entries),
  1088. getBaseState());
  1089. }
  1090. /**
  1091. * Clear the configuration file
  1092. */
  1093. protected void clear() {
  1094. state.set(newState());
  1095. }
  1096. /**
  1097. * Check if bytes should be treated as UTF-8 or not.
  1098. *
  1099. * @param bytes
  1100. * the bytes to check encoding for.
  1101. * @return true if bytes should be treated as UTF-8, false otherwise.
  1102. * @since 4.4
  1103. */
  1104. protected boolean isUtf8(final byte[] bytes) {
  1105. return bytes.length >= 3 && bytes[0] == (byte) 0xEF
  1106. && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF;
  1107. }
  1108. private static String readSectionName(StringReader in)
  1109. throws ConfigInvalidException {
  1110. final StringBuilder name = new StringBuilder();
  1111. for (;;) {
  1112. int c = in.read();
  1113. if (c < 0)
  1114. throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
  1115. if (']' == c) {
  1116. in.reset();
  1117. break;
  1118. }
  1119. if (' ' == c || '\t' == c) {
  1120. for (;;) {
  1121. c = in.read();
  1122. if (c < 0)
  1123. throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
  1124. if ('"' == c) {
  1125. in.reset();
  1126. break;
  1127. }
  1128. if (' ' == c || '\t' == c)
  1129. continue; // Skipped...
  1130. throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
  1131. }
  1132. break;
  1133. }
  1134. if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c)
  1135. name.append((char) c);
  1136. else
  1137. throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
  1138. }
  1139. return name.toString();
  1140. }
  1141. private static String readKeyName(StringReader in)
  1142. throws ConfigInvalidException {
  1143. final StringBuilder name = new StringBuilder();
  1144. for (;;) {
  1145. int c = in.read();
  1146. if (c < 0)
  1147. throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
  1148. if ('=' == c)
  1149. break;
  1150. if (' ' == c || '\t' == c) {
  1151. for (;;) {
  1152. c = in.read();
  1153. if (c < 0)
  1154. throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
  1155. if ('=' == c)
  1156. break;
  1157. if (';' == c || '#' == c || '\n' == c) {
  1158. in.reset();
  1159. break;
  1160. }
  1161. if (' ' == c || '\t' == c)
  1162. continue; // Skipped...
  1163. throw new ConfigInvalidException(JGitText.get().badEntryDelimiter);
  1164. }
  1165. break;
  1166. }
  1167. if (Character.isLetterOrDigit((char) c) || c == '-') {
  1168. // From the git-config man page:
  1169. // The variable names are case-insensitive and only
  1170. // alphanumeric characters and - are allowed.
  1171. name.append((char) c);
  1172. } else if ('\n' == c) {
  1173. in.reset();
  1174. name.append((char) c);
  1175. break;
  1176. } else
  1177. throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badEntryName, name));
  1178. }
  1179. return name.toString();
  1180. }
  1181. private static String readSubsectionName(StringReader in)
  1182. throws ConfigInvalidException {
  1183. StringBuilder r = new StringBuilder();
  1184. for (;;) {
  1185. int c = in.read();
  1186. if (c < 0) {
  1187. break;
  1188. }
  1189. if ('\n' == c) {
  1190. throw new ConfigInvalidException(
  1191. JGitText.get().newlineInQuotesNotAllowed);
  1192. }
  1193. if ('\\' == c) {
  1194. c = in.read();
  1195. switch (c) {
  1196. case -1:
  1197. throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
  1198. case '\\':
  1199. case '"':
  1200. r.append((char) c);
  1201. continue;
  1202. default:
  1203. // C git simply drops backslashes if the escape sequence is not
  1204. // recognized.
  1205. r.append((char) c);
  1206. continue;
  1207. }
  1208. }
  1209. if ('"' == c) {
  1210. break;
  1211. }
  1212. r.append((char) c);
  1213. }
  1214. return r.toString();
  1215. }
  1216. private static String readValue(StringReader in)
  1217. throws ConfigInvalidException {
  1218. StringBuilder value = new StringBuilder();
  1219. StringBuilder trailingSpaces = null;
  1220. boolean quote = false;
  1221. boolean inLeadingSpace = true;
  1222. for (;;) {
  1223. int c = in.read();
  1224. if (c < 0) {
  1225. break;
  1226. }
  1227. if ('\n' == c) {
  1228. if (quote) {
  1229. throw new ConfigInvalidException(
  1230. JGitText.get().newlineInQuotesNotAllowed);
  1231. }
  1232. in.reset();
  1233. break;
  1234. }
  1235. if (!quote && (';' == c || '#' == c)) {
  1236. if (trailingSpaces != null) {
  1237. trailingSpaces.setLength(0);
  1238. }
  1239. in.reset();
  1240. break;
  1241. }
  1242. char cc = (char) c;
  1243. if (Character.isWhitespace(cc)) {
  1244. if (inLeadingSpace) {
  1245. continue;
  1246. }
  1247. if (trailingSpaces == null) {
  1248. trailingSpaces = new StringBuilder();
  1249. }
  1250. trailingSpaces.append(cc);
  1251. continue;
  1252. }
  1253. inLeadingSpace = false;
  1254. if (trailingSpaces != null) {
  1255. value.append(trailingSpaces);
  1256. trailingSpaces.setLength(0);
  1257. }
  1258. if ('\\' == c) {
  1259. c = in.read();
  1260. switch (c) {
  1261. case -1:
  1262. throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
  1263. case '\n':
  1264. continue;
  1265. case 't':
  1266. value.append('\t');
  1267. continue;
  1268. case 'b':
  1269. value.append('\b');
  1270. continue;
  1271. case 'n':
  1272. value.append('\n');
  1273. continue;
  1274. case '\\':
  1275. value.append('\\');
  1276. continue;
  1277. case '"':
  1278. value.append('"');
  1279. continue;
  1280. case '\r': {
  1281. int next = in.read();
  1282. if (next == '\n') {
  1283. continue; // CR-LF
  1284. } else if (next >= 0) {
  1285. in.reset();
  1286. }
  1287. break;
  1288. }
  1289. default:
  1290. break;
  1291. }
  1292. throw new ConfigInvalidException(
  1293. MessageFormat.format(JGitText.get().badEscape,
  1294. Character.isAlphabetic(c)
  1295. ? Character.valueOf(((char) c))
  1296. : toUnicodeLiteral(c)));
  1297. }
  1298. if ('"' == c) {
  1299. quote = !quote;
  1300. continue;
  1301. }
  1302. value.append(cc);
  1303. }
  1304. return value.length() > 0 ? value.toString() : null;
  1305. }
  1306. private static String toUnicodeLiteral(int c) {
  1307. return String.format("\\u%04x", //$NON-NLS-1$
  1308. Integer.valueOf(c));
  1309. }
  1310. /**
  1311. * Parses a section of the configuration into an application model object.
  1312. * <p>
  1313. * Instances must implement hashCode and equals such that model objects can
  1314. * be cached by using the {@code SectionParser} as a key of a HashMap.
  1315. * <p>
  1316. * As the {@code SectionParser} itself is used as the key of the internal
  1317. * HashMap applications should be careful to ensure the SectionParser key
  1318. * does not retain unnecessary application state which may cause memory to
  1319. * be held longer than expected.
  1320. *
  1321. * @param <T>
  1322. * type of the application model created by the parser.
  1323. */
  1324. public static interface SectionParser<T> {
  1325. /**
  1326. * Create a model object from a configuration.
  1327. *
  1328. * @param cfg
  1329. * the configuration to read values from.
  1330. * @return the application model instance.
  1331. */
  1332. T parse(Config cfg);
  1333. }
  1334. private static class StringReader {
  1335. private final char[] buf;
  1336. private int pos;
  1337. StringReader(String in) {
  1338. buf = in.toCharArray();
  1339. }
  1340. int read() {
  1341. if (pos >= buf.length) {
  1342. return -1;
  1343. }
  1344. return buf[pos++];
  1345. }
  1346. void reset() {
  1347. pos--;
  1348. }
  1349. }
  1350. /**
  1351. * Converts enumeration values into configuration options and vice-versa,
  1352. * allowing to match a config option with an enum value.
  1353. *
  1354. */
  1355. public static interface ConfigEnum {
  1356. /**
  1357. * Converts enumeration value into a string to be save in config.
  1358. *
  1359. * @return the enum value as config string
  1360. */
  1361. String toConfigValue();
  1362. /**
  1363. * Checks if the given string matches with enum value.
  1364. *
  1365. * @param in
  1366. * the string to match
  1367. * @return true if the given string matches enum value, false otherwise
  1368. */
  1369. boolean matchConfigValue(String in);
  1370. }
  1371. }