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

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