Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

ConfigManager.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. /*
  2. * Copyright (c) 2006-2015 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.ClientInfo;
  29. import com.dmdirc.util.validators.Validator;
  30. import com.google.common.collect.ArrayListMultimap;
  31. import com.google.common.collect.Multimap;
  32. import java.util.ArrayList;
  33. import java.util.Collection;
  34. import java.util.Collections;
  35. import java.util.HashMap;
  36. import java.util.HashSet;
  37. import java.util.List;
  38. import java.util.Map;
  39. import java.util.Set;
  40. import java.util.TreeMap;
  41. import org.slf4j.Logger;
  42. import org.slf4j.LoggerFactory;
  43. /**
  44. * The config manager manages the various config sources for each entity.
  45. */
  46. class ConfigManager implements ConfigChangeListener, ConfigProviderListener,
  47. AggregateConfigProvider {
  48. private static final Logger LOG = LoggerFactory.getLogger(ConfigManager.class);
  49. /** Temporary map for lookup stats. */
  50. private static final Map<String, Integer> STATS = new TreeMap<>();
  51. /** Magical domain to redirect to the version identity. */
  52. private static final String VERSION_DOMAIN = "version";
  53. /** A list of sources for this config manager. */
  54. private final List<ConfigFileBackedConfigProvider> sources = new ArrayList<>();
  55. /** The listeners registered for this manager. */
  56. private final Multimap<String, ConfigChangeListener> listeners = ArrayListMultimap.create();
  57. /** The config binder to use for this manager. */
  58. private final ConfigBinder binder;
  59. /** The manager to use to fetch global state. */
  60. private final IdentityManager manager;
  61. /** Client info object. */
  62. private final ClientInfo clientInfo;
  63. /** The protocol this manager is for. */
  64. private String protocol;
  65. /** The ircd this manager is for. */
  66. private String ircd;
  67. /** The network this manager is for. */
  68. private String network;
  69. /** The server this manager is for. */
  70. private String server;
  71. /** The channel this manager is for. */
  72. private String channel;
  73. /**
  74. * Creates a new instance of ConfigManager.
  75. *
  76. * @param manager The manager to use to retrieve global state horribly.
  77. * @param protocol The protocol for this manager
  78. * @param ircd The name of the ircd for this manager
  79. * @param network The name of the network for this manager
  80. * @param server The name of the server for this manager
  81. *
  82. * @since 0.6.3
  83. */
  84. ConfigManager(
  85. final ClientInfo clientInfo,
  86. final IdentityManager manager,
  87. final String protocol, final String ircd,
  88. final String network, final String server) {
  89. this(clientInfo, manager, protocol, ircd, network, server, "<Unknown>");
  90. }
  91. /**
  92. * Creates a new instance of ConfigManager.
  93. *
  94. * @param manager The manager to use to retrieve global state horribly.
  95. * @param protocol The protocol for this manager
  96. * @param ircd The name of the ircd for this manager
  97. * @param network The name of the network for this manager
  98. * @param server The name of the server for this manager
  99. * @param channel The name of the channel for this manager
  100. *
  101. * @since 0.6.3
  102. */
  103. ConfigManager(
  104. final ClientInfo clientInfo,
  105. final IdentityManager manager,
  106. final String protocol, final String ircd,
  107. final String network, final String server, final String channel) {
  108. final String chanName = channel + '@' + network;
  109. this.clientInfo = clientInfo;
  110. this.manager = manager;
  111. this.protocol = protocol;
  112. this.ircd = ircd;
  113. this.network = network;
  114. this.server = server;
  115. this.channel = chanName;
  116. binder = new ConfigBinder(this);
  117. }
  118. @Override
  119. public ConfigBinder getBinder() {
  120. return binder;
  121. }
  122. @Override
  123. public String getOption(final String domain, final String option,
  124. final Validator<String> validator) {
  125. doStats(domain, option);
  126. if (VERSION_DOMAIN.equals(domain)) {
  127. final String response = clientInfo.getVersionConfigSetting(VERSION_DOMAIN, option);
  128. if (response == null || validator.validate(response).isFailure()) {
  129. return null;
  130. }
  131. return response;
  132. }
  133. synchronized (sources) {
  134. for (ConfigProvider source : sources) {
  135. if (source.hasOption(domain, option, validator)) {
  136. return source.getOption(domain, option, validator);
  137. }
  138. }
  139. }
  140. return null;
  141. }
  142. @Override
  143. public boolean hasOption(final String domain, final String option,
  144. final Validator<String> validator) {
  145. doStats(domain, option);
  146. if (VERSION_DOMAIN.equals(domain)) {
  147. final String response = clientInfo.getVersionConfigSetting(VERSION_DOMAIN, option);
  148. return response != null && !validator.validate(response).isFailure();
  149. }
  150. synchronized (sources) {
  151. for (ConfigProvider source : sources) {
  152. if (source.hasOption(domain, option, validator)) {
  153. return true;
  154. }
  155. }
  156. }
  157. return false;
  158. }
  159. @Override
  160. public Map<String, String> getOptions(final String domain) {
  161. if (VERSION_DOMAIN.equals(domain)) {
  162. return manager.getVersionSettings().getOptions(domain);
  163. }
  164. final Map<String, String> res = new HashMap<>();
  165. synchronized (sources) {
  166. for (int i = sources.size() - 1; i >= 0; i--) {
  167. res.putAll(sources.get(i).getOptions(domain));
  168. }
  169. }
  170. return res;
  171. }
  172. /**
  173. * Removes the specified identity from this manager.
  174. *
  175. * @param identity The identity to be removed
  176. */
  177. public void removeIdentity(final ConfigProvider identity) {
  178. if (!sources.contains(identity)) {
  179. return;
  180. }
  181. final Collection<String[]> changed = new ArrayList<>();
  182. // Determine which settings will have changed
  183. for (String domain : identity.getDomains()) {
  184. identity.getOptions(domain).keySet().stream()
  185. .filter(option -> identity.equals(getScope(domain, option)))
  186. .forEach(option -> changed.add(new String[]{domain, option}));
  187. }
  188. synchronized (sources) {
  189. identity.removeListener(this);
  190. sources.remove(identity);
  191. }
  192. // Fire change listeners
  193. for (String[] setting : changed) {
  194. configChanged(setting[0], setting[1]);
  195. }
  196. }
  197. /**
  198. * Retrieves the identity that currently defines the specified domain and option.
  199. *
  200. * @param domain The domain to search for
  201. * @param option The option to search for
  202. *
  203. * @return The identity that defines that setting, or null on failure
  204. */
  205. protected ConfigProvider getScope(final String domain, final String option) {
  206. if (VERSION_DOMAIN.equals(domain)) {
  207. return manager.getVersionSettings();
  208. }
  209. synchronized (sources) {
  210. for (ConfigProvider source : sources) {
  211. if (source.hasOptionString(domain, option)) {
  212. return source;
  213. }
  214. }
  215. }
  216. return null;
  217. }
  218. /**
  219. * Checks whether the specified identity applies to this config manager.
  220. *
  221. * @param identity The identity to test
  222. *
  223. * @return True if the identity applies, false otherwise
  224. */
  225. public boolean identityApplies(final ConfigFileBackedConfigProvider identity) {
  226. final String comp;
  227. switch (identity.getTarget().getType()) {
  228. case PROTOCOL:
  229. comp = protocol;
  230. break;
  231. case IRCD:
  232. comp = ircd;
  233. break;
  234. case NETWORK:
  235. comp = network;
  236. break;
  237. case SERVER:
  238. comp = server;
  239. break;
  240. case CHANNEL:
  241. comp = channel;
  242. break;
  243. case CUSTOM:
  244. // We don't want custom identities
  245. comp = null;
  246. break;
  247. default:
  248. comp = "";
  249. break;
  250. }
  251. final boolean result = comp != null
  252. && identityTargetMatches(identity.getTarget().getData(), comp);
  253. LOG.trace("Checking if identity {} applies. Comparison: {}, target: {}, result: {}",
  254. identity, comp, identity.getTarget().getData(), result);
  255. return result;
  256. }
  257. /**
  258. * Determines whether the specified identity target matches the desired target. If the desired
  259. * target is prefixed with "re:", it is treated as a regular expression; otherwise the strings
  260. * are compared lexicographically to determine a match.
  261. *
  262. * @param desired The target string required by this config manager
  263. * @param actual The target string supplied by the identity
  264. *
  265. * @return True if the identity should be applied, false otherwise
  266. *
  267. * @since 0.6.3m2
  268. */
  269. protected boolean identityTargetMatches(final String actual, final String desired) {
  270. return actual.startsWith("re:") ? desired.matches(actual.substring(3))
  271. : actual.equalsIgnoreCase(desired);
  272. }
  273. /**
  274. * Called whenever there is a new identity available. Checks if the identity is relevant for
  275. * this manager, and adds it if it is.
  276. *
  277. * @param identity The identity to be checked
  278. */
  279. public void checkIdentity(final ConfigFileBackedConfigProvider identity) {
  280. if (!sources.contains(identity) && identityApplies(identity)) {
  281. synchronized (sources) {
  282. sources.add(identity);
  283. identity.addListener(this);
  284. Collections.sort(sources, new ConfigProviderTargetComparator());
  285. }
  286. // Determine which settings will have changed
  287. for (String domain : identity.getDomains()) {
  288. identity.getOptions(domain).keySet().stream()
  289. .filter(option -> identity.equals(getScope(domain, option)))
  290. .forEach(option -> configChanged(domain, option));
  291. }
  292. }
  293. }
  294. @Override
  295. public Set<String> getDomains() {
  296. final Set<String> res = new HashSet<>();
  297. synchronized (sources) {
  298. for (ConfigProvider source : sources) {
  299. res.addAll(source.getDomains());
  300. }
  301. }
  302. return res;
  303. }
  304. @Override
  305. public List<ConfigProvider> getSources() {
  306. return new ArrayList<>(sources);
  307. }
  308. /**
  309. * Migrates this manager from its current configuration to the appropriate one for the specified
  310. * new parameters, firing listeners where settings have changed.
  311. *
  312. * <p>
  313. * This is package private - only callers with access to a {@link ConfigProviderMigrator}
  314. * should be able to migrate managers.
  315. *
  316. * @param protocol The protocol for this manager
  317. * @param ircd The new name of the ircd for this manager
  318. * @param network The new name of the network for this manager
  319. * @param server The new name of the server for this manager
  320. * @param channel The new name of the channel for this manager
  321. */
  322. void migrate(final String protocol, final String ircd,
  323. final String network, final String server, final String channel) {
  324. LOG.debug("Migrating from {{}, {}, {}, {}, {}} to {{}, {}, {}, {}, {}}", this.protocol,
  325. this.ircd, this.network, this.server, this.channel, protocol, ircd, network, server,
  326. channel);
  327. this.protocol = protocol;
  328. this.ircd = ircd;
  329. this.network = network;
  330. this.server = server;
  331. this.channel = channel + '@' + network;
  332. new ArrayList<>(sources).stream().filter(identity -> !identityApplies(identity))
  333. .forEach(identity -> {
  334. LOG.debug("Removing identity that no longer applies: {}", identity);
  335. removeIdentity(identity);
  336. });
  337. final List<ConfigFileBackedConfigProvider> newSources = manager.getIdentitiesForManager(this);
  338. for (ConfigFileBackedConfigProvider identity : newSources) {
  339. LOG.trace("Testing new identity: {}", identity);
  340. checkIdentity(identity);
  341. }
  342. LOG.debug("New identities: {}", sources);
  343. }
  344. /**
  345. * Records the lookup request for the specified domain and option.
  346. *
  347. * @param domain The domain that is being looked up
  348. * @param option The option that is being looked up
  349. */
  350. @SuppressWarnings("PMD.AvoidCatchingNPE")
  351. protected static void doStats(final String domain, final String option) {
  352. final String key = domain + '.' + option;
  353. try {
  354. STATS.put(key, 1 + (STATS.containsKey(key) ? STATS.get(key) : 0));
  355. } catch (NullPointerException ex) {
  356. // JVM bugs ftl.
  357. }
  358. }
  359. /**
  360. * Retrieves the statistic map.
  361. *
  362. * @return A map of config options to lookup counts
  363. */
  364. public static Map<String, Integer> getStats() {
  365. return STATS;
  366. }
  367. @Override
  368. public void addChangeListener(final String domain,
  369. final ConfigChangeListener listener) {
  370. addListener(domain, listener);
  371. }
  372. @Override
  373. public void addChangeListener(final String domain, final String key,
  374. final ConfigChangeListener listener) {
  375. addListener(domain + '.' + key, listener);
  376. }
  377. @Override
  378. public void removeListener(final ConfigChangeListener listener) {
  379. synchronized (listeners) {
  380. final Iterable<String> keys = new HashSet<>(listeners.keySet());
  381. keys.forEach(k -> listeners.remove(k, listener));
  382. }
  383. }
  384. /**
  385. * Adds the specified listener to the internal map/list.
  386. *
  387. * @param key The key to use (domain or domain.key)
  388. * @param listener The listener to register
  389. */
  390. private void addListener(final String key,
  391. final ConfigChangeListener listener) {
  392. synchronized (listeners) {
  393. listeners.put(key, listener);
  394. }
  395. }
  396. @Override
  397. public void configChanged(final String domain, final String key) {
  398. final Collection<ConfigChangeListener> targets = new ArrayList<>();
  399. if (listeners.containsKey(domain)) {
  400. targets.addAll(listeners.get(domain));
  401. }
  402. if (listeners.containsKey(domain + '.' + key)) {
  403. targets.addAll(listeners.get(domain + '.' + key));
  404. }
  405. for (ConfigChangeListener listener : targets) {
  406. listener.configChanged(domain, key);
  407. }
  408. }
  409. @Override
  410. public void configProviderAdded(final ConfigFileBackedConfigProvider configProvider) {
  411. checkIdentity(configProvider);
  412. }
  413. @Override
  414. public void configProviderRemoved(final ConfigFileBackedConfigProvider configProvider) {
  415. removeIdentity(configProvider);
  416. }
  417. }