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

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