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.

ConfigManager.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. /*
  2. * Copyright (c) 2006-2013 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.config;
  23. import com.dmdirc.interfaces.config.AggregateConfigProvider;
  24. import com.dmdirc.interfaces.config.ConfigChangeListener;
  25. import com.dmdirc.interfaces.config.ConfigProvider;
  26. import com.dmdirc.interfaces.config.ConfigProviderListener;
  27. import com.dmdirc.interfaces.config.ConfigProviderMigrator;
  28. import com.dmdirc.util.collections.MapList;
  29. import com.dmdirc.util.validators.Validator;
  30. import java.util.ArrayList;
  31. import java.util.Collections;
  32. import java.util.HashMap;
  33. import java.util.HashSet;
  34. import java.util.List;
  35. import java.util.Map;
  36. import java.util.Set;
  37. import java.util.TreeMap;
  38. import lombok.Getter;
  39. import lombok.extern.slf4j.Slf4j;
  40. /**
  41. * The config manager manages the various config sources for each entity.
  42. */
  43. @Slf4j
  44. @SuppressWarnings("PMD.UnusedPrivateField")
  45. public class ConfigManager extends BaseConfigProvider implements ConfigChangeListener,
  46. ConfigProviderListener, AggregateConfigProvider {
  47. /** Temporary map for lookup stats. */
  48. private static final Map<String, Integer> STATS = new TreeMap<>();
  49. /** Magical domain to redirect to the version identity. */
  50. private static final String VERSION_DOMAIN = "version";
  51. /** A list of sources for this config manager. */
  52. private final List<ConfigProvider> sources;
  53. /** The listeners registered for this manager. */
  54. private final MapList<String, ConfigChangeListener> listeners = new MapList<>();
  55. /** The config binder to use for this manager. */
  56. @Getter
  57. private final ConfigBinder binder = new ConfigBinder(this);
  58. /** The protocol this manager is for. */
  59. private String protocol;
  60. /** The ircd this manager is for. */
  61. private String ircd;
  62. /** The network this manager is for. */
  63. private String network;
  64. /** The server this manager is for. */
  65. private String server;
  66. /** The channel this manager is for. */
  67. private String channel;
  68. /**
  69. * Creates a new instance of ConfigManager.
  70. *
  71. * @param protocol The protocol for this manager
  72. * @param ircd The name of the ircd for this manager
  73. * @param network The name of the network for this manager
  74. * @param server The name of the server for this manager
  75. * @since 0.6.3
  76. */
  77. public ConfigManager(final String protocol, final String ircd,
  78. final String network, final String server) {
  79. this(protocol, ircd, network, server, "<Unknown>");
  80. }
  81. /**
  82. * Creates a new instance of ConfigManager.
  83. *
  84. * @param protocol The protocol for this manager
  85. * @param ircd The name of the ircd for this manager
  86. * @param network The name of the network for this manager
  87. * @param server The name of the server for this manager
  88. * @param channel The name of the channel for this manager
  89. * @since 0.6.3
  90. */
  91. public ConfigManager(final String protocol, final String ircd,
  92. final String network, final String server, final String channel) {
  93. final String chanName = channel + "@" + network;
  94. this.protocol = protocol;
  95. this.ircd = ircd;
  96. this.network = network;
  97. this.server = server;
  98. this.channel = chanName;
  99. sources = IdentityManager.getIdentityManager().getIdentitiesForManager(this);
  100. log.debug("Found {} source(s) for protocol: {}, ircd: {}, network: {}, server: {}, channel: {}",
  101. new Object[] { sources.size(), protocol, ircd, network, server, chanName });
  102. for (ConfigProvider identity : sources) {
  103. log.trace("Found {}", identity);
  104. identity.addListener(this);
  105. }
  106. IdentityManager.getIdentityManager().registerIdentityListener(this);
  107. }
  108. /** {@inheritDoc} */
  109. @Override
  110. public String getOption(final String domain, final String option,
  111. final Validator<String> validator) {
  112. doStats(domain, option);
  113. if (VERSION_DOMAIN.equals(domain)) {
  114. return IdentityManager.getIdentityManager()
  115. .getVersionSettings().getOption(domain, option, validator);
  116. }
  117. synchronized (sources) {
  118. for (ConfigProvider source : sources) {
  119. if (source.hasOption(domain, option, validator)) {
  120. return source.getOption(domain, option, validator);
  121. }
  122. }
  123. }
  124. return null;
  125. }
  126. /** {@inheritDoc} */
  127. @Override
  128. public boolean hasOption(final String domain, final String option,
  129. final Validator<String> validator) {
  130. doStats(domain, option);
  131. if (VERSION_DOMAIN.equals(domain)) {
  132. return IdentityManager.getIdentityManager()
  133. .getVersionSettings().hasOption(domain, option, validator);
  134. }
  135. synchronized (sources) {
  136. for (ConfigProvider source : sources) {
  137. if (source.hasOption(domain, option, validator)) {
  138. return true;
  139. }
  140. }
  141. }
  142. return false;
  143. }
  144. /** {@inheritDoc} */
  145. @Override
  146. public Map<String, String> getOptions(final String domain) {
  147. if (VERSION_DOMAIN.equals(domain)) {
  148. return IdentityManager.getIdentityManager()
  149. .getVersionSettings().getOptions(domain);
  150. }
  151. final Map<String, String> res = new HashMap<>();
  152. synchronized (sources) {
  153. for (int i = sources.size() - 1; i >= 0; i--) {
  154. res.putAll(sources.get(i).getOptions(domain));
  155. }
  156. }
  157. return res;
  158. }
  159. /**
  160. * Removes the specified identity from this manager.
  161. *
  162. * @param identity The identity to be removed
  163. */
  164. public void removeIdentity(final ConfigProvider identity) {
  165. if (!sources.contains(identity)) {
  166. return;
  167. }
  168. final List<String[]> changed = new ArrayList<>();
  169. // Determine which settings will have changed
  170. for (String domain : identity.getDomains()) {
  171. for (String option : identity.getOptions(domain).keySet()) {
  172. if (identity.equals(getScope(domain, option))) {
  173. changed.add(new String[]{domain, option});
  174. }
  175. }
  176. }
  177. synchronized (sources) {
  178. identity.removeListener(this);
  179. sources.remove(identity);
  180. }
  181. // Fire change listeners
  182. for (String[] setting : changed) {
  183. configChanged(setting[0], setting[1]);
  184. }
  185. }
  186. /**
  187. * Retrieves the identity that currently defines the specified domain and
  188. * option.
  189. *
  190. * @param domain The domain to search for
  191. * @param option The option to search for
  192. * @return The identity that defines that setting, or null on failure
  193. */
  194. protected ConfigProvider getScope(final String domain, final String option) {
  195. if (VERSION_DOMAIN.equals(domain)) {
  196. return IdentityManager.getIdentityManager()
  197. .getVersionSettings();
  198. }
  199. synchronized (sources) {
  200. for (ConfigProvider source : sources) {
  201. if (source.hasOptionString(domain, option)) {
  202. return source;
  203. }
  204. }
  205. }
  206. return null;
  207. }
  208. /**
  209. * Checks whether the specified identity applies to this config manager.
  210. *
  211. * @param identity The identity to test
  212. * @return True if the identity applies, false otherwise
  213. */
  214. public boolean identityApplies(final ConfigProvider identity) {
  215. String comp;
  216. switch (identity.getTarget().getType()) {
  217. case PROTOCOL:
  218. comp = protocol;
  219. break;
  220. case IRCD:
  221. comp = ircd;
  222. break;
  223. case NETWORK:
  224. comp = network;
  225. break;
  226. case SERVER:
  227. comp = server;
  228. break;
  229. case CHANNEL:
  230. comp = channel;
  231. break;
  232. case CUSTOM:
  233. // We don't want custom identities
  234. comp = null;
  235. break;
  236. default:
  237. comp = "";
  238. break;
  239. }
  240. final boolean result = comp != null
  241. && identityTargetMatches(identity.getTarget().getData(), comp);
  242. log.trace("Checking if identity {} applies. Comparison: {}, target: {}, result: {}",
  243. new Object[] { identity, comp, identity.getTarget().getData(), result });
  244. return result;
  245. }
  246. /**
  247. * Determines whether the specified identity target matches the desired
  248. * target. If the desired target is prefixed with "re:", it is treated
  249. * as a regular expression; otherwise the strings are compared
  250. * lexigraphically to determine a match.
  251. *
  252. * @param desired The target string required by this config manager
  253. * @param actual The target string supplied by the identity
  254. * @return True if the identity should be applied, false otherwise
  255. * @since 0.6.3m2
  256. */
  257. protected boolean identityTargetMatches(final String actual, final String desired) {
  258. return actual.startsWith("re:") ? desired.matches(actual.substring(3))
  259. : actual.equalsIgnoreCase(desired);
  260. }
  261. /**
  262. * Called whenever there is a new identity available. Checks if the
  263. * identity is relevant for this manager, and adds it if it is.
  264. *
  265. * @param identity The identity to be checked
  266. */
  267. public void checkIdentity(final ConfigProvider identity) {
  268. if (!sources.contains(identity) && identityApplies(identity)) {
  269. synchronized (sources) {
  270. sources.add(identity);
  271. identity.addListener(this);
  272. Collections.sort(sources, new ConfigProviderTargetComparator());
  273. }
  274. // Determine which settings will have changed
  275. for (String domain : identity.getDomains()) {
  276. for (String option : identity.getOptions(domain).keySet()) {
  277. if (identity.equals(getScope(domain, option))) {
  278. configChanged(domain, option);
  279. }
  280. }
  281. }
  282. }
  283. }
  284. /** {@inheritDoc} */
  285. @Override
  286. public Set<String> getDomains() {
  287. final Set<String> res = new HashSet<>();
  288. synchronized (sources) {
  289. for (ConfigProvider source : sources) {
  290. res.addAll(source.getDomains());
  291. }
  292. }
  293. return res;
  294. }
  295. /** {@inheritDoc} */
  296. @Override
  297. public List<ConfigProvider> getSources() {
  298. return new ArrayList<>(sources);
  299. }
  300. /**
  301. * Migrates this manager from its current configuration to the
  302. * appropriate one for the specified new parameters, firing listeners where
  303. * settings have changed.
  304. *
  305. * <p>This is package private - only callers with access to a {@link ConfigProviderMigrator}
  306. * should be able to migrate managers.
  307. *
  308. * @param protocol The protocol for this manager
  309. * @param ircd The new name of the ircd for this manager
  310. * @param network The new name of the network for this manager
  311. * @param server The new name of the server for this manager
  312. * @param channel The new name of the channel for this manager
  313. */
  314. void migrate(final String protocol, final String ircd,
  315. final String network, final String server, final String channel) {
  316. log.debug("Migrating from {{}, {}, {}, {}, {}} to {{}, {}, {}, {}, {}}",
  317. new Object[] {
  318. this.protocol, this.ircd, this.network, this.server, this.channel,
  319. protocol, ircd, network, server, channel,
  320. });
  321. this.protocol = protocol;
  322. this.ircd = ircd;
  323. this.network = network;
  324. this.server = server;
  325. this.channel = channel + "@" + network;
  326. for (ConfigProvider identity : new ArrayList<>(sources)) {
  327. if (!identityApplies(identity)) {
  328. log.debug("Removing identity that no longer applies: {}", identity);
  329. removeIdentity(identity);
  330. }
  331. }
  332. final List<ConfigProvider> newSources = IdentityManager.getIdentityManager()
  333. .getIdentitiesForManager(this);
  334. for (ConfigProvider identity : newSources) {
  335. log.trace("Testing new identity: {}", identity);
  336. checkIdentity(identity);
  337. }
  338. log.debug("New identities: {}", sources);
  339. }
  340. /**
  341. * Records the lookup request for the specified domain and option.
  342. *
  343. * @param domain The domain that is being looked up
  344. * @param option The option that is being looked up
  345. */
  346. @SuppressWarnings("PMD.AvoidCatchingNPE")
  347. protected static void doStats(final String domain, final String option) {
  348. final String key = domain + "." + option;
  349. try {
  350. STATS.put(key, 1 + (STATS.containsKey(key) ? STATS.get(key) : 0));
  351. } catch (NullPointerException ex) {
  352. // JVM bugs ftl.
  353. }
  354. }
  355. /**
  356. * Retrieves the statistic map.
  357. *
  358. * @return A map of config options to lookup counts
  359. */
  360. public static Map<String, Integer> getStats() {
  361. return STATS;
  362. }
  363. /** {@inheritDoc} */
  364. @Override
  365. public void addChangeListener(final String domain,
  366. final ConfigChangeListener listener) {
  367. addListener(domain, listener);
  368. }
  369. /** {@inheritDoc} */
  370. @Override
  371. public void addChangeListener(final String domain, final String key,
  372. final ConfigChangeListener listener) {
  373. addListener(domain + "." + key, listener);
  374. }
  375. /** {@inheritDoc} */
  376. @Override
  377. public void removeListener(final ConfigChangeListener listener) {
  378. synchronized (listeners) {
  379. listeners.removeFromAll(listener);
  380. }
  381. }
  382. /**
  383. * Adds the specified listener to the internal map/list.
  384. *
  385. * @param key The key to use (domain or domain.key)
  386. * @param listener The listener to register
  387. */
  388. private void addListener(final String key,
  389. final ConfigChangeListener listener) {
  390. synchronized (listeners) {
  391. listeners.add(key, listener);
  392. }
  393. }
  394. /** {@inheritDoc} */
  395. @Override
  396. public void configChanged(final String domain, final String key) {
  397. final List<ConfigChangeListener> targets = new ArrayList<>();
  398. if (listeners.containsKey(domain)) {
  399. targets.addAll(listeners.get(domain));
  400. }
  401. if (listeners.containsKey(domain + "." + key)) {
  402. targets.addAll(listeners.get(domain + "." + key));
  403. }
  404. for (ConfigChangeListener listener : targets) {
  405. listener.configChanged(domain, key);
  406. }
  407. }
  408. /** {@inheritDoc} */
  409. @Override
  410. public void configProviderAdded(final ConfigProvider configProvider) {
  411. checkIdentity(configProvider);
  412. }
  413. /** {@inheritDoc} */
  414. @Override
  415. public void configProviderRemoved(final ConfigProvider configProvider) {
  416. removeIdentity(configProvider);
  417. }
  418. }