Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

ActionManager.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. /*
  2. * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
  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.actions;
  23. import com.dmdirc.Main;
  24. import com.dmdirc.Precondition;
  25. import com.dmdirc.actions.interfaces.ActionComparison;
  26. import com.dmdirc.actions.interfaces.ActionComponent;
  27. import com.dmdirc.actions.interfaces.ActionType;
  28. import com.dmdirc.actions.wrappers.AliasWrapper;
  29. import com.dmdirc.actions.wrappers.PerformWrapper;
  30. import com.dmdirc.config.IdentityManager;
  31. import com.dmdirc.interfaces.ActionListener;
  32. import com.dmdirc.interfaces.ConfigChangeListener;
  33. import com.dmdirc.logger.ErrorLevel;
  34. import com.dmdirc.logger.Logger;
  35. import com.dmdirc.updater.components.ActionGroupComponent;
  36. import com.dmdirc.util.MapList;
  37. import com.dmdirc.util.resourcemanager.ZipResourceManager;
  38. import java.io.File;
  39. import java.io.IOException;
  40. import java.util.ArrayList;
  41. import java.util.HashMap;
  42. import java.util.List;
  43. import java.util.Map;
  44. /**
  45. * Manages all actions for the client.
  46. *
  47. * @author chris
  48. */
  49. public final class ActionManager {
  50. /** A list of registered action types. */
  51. private final static List<ActionType> actionTypes
  52. = new ArrayList<ActionType>();
  53. /** A list of registered action components. */
  54. private final static List<ActionComponent> actionComponents
  55. = new ArrayList<ActionComponent>();
  56. /** A list of registered action comparisons. */
  57. private final static List<ActionComparison> actionComparisons
  58. = new ArrayList<ActionComparison>();
  59. /** A map linking types and a list of actions that're registered for them. */
  60. private final static MapList<ActionType, Action> actions
  61. = new MapList<ActionType, Action>();
  62. /** A map linking groups and a list of actions that're in them. */
  63. private final static Map<String, ActionGroup> groups
  64. = new HashMap<String, ActionGroup>();
  65. /** A map of the action type groups to the action types within. */
  66. private final static MapList<String, ActionType> actionTypeGroups
  67. = new MapList<String, ActionType>();
  68. /** The listeners that we have registered. */
  69. private final static MapList<ActionType, ActionListener> listeners
  70. = new MapList<ActionType, ActionListener>();
  71. /** Indicates whether or not user actions should be killed (not processed). */
  72. private static boolean killSwitch
  73. = IdentityManager.getGlobalConfig().getOptionBool("actions", "killswitch");
  74. /** Creates a new instance of ActionManager. */
  75. private ActionManager() {
  76. // Shouldn't be instansiated
  77. }
  78. /**
  79. * Initialises the action manager.
  80. */
  81. public static void init() {
  82. registerActionTypes(CoreActionType.values());
  83. registerActionComparisons(CoreActionComparison.values());
  84. registerActionComponents(CoreActionComponent.values());
  85. registerGroup(AliasWrapper.getAliasWrapper());
  86. registerGroup(PerformWrapper.getPerformWrapper());
  87. // Register a listener for the closing event, so we can save actions
  88. addListener(new ActionListener() {
  89. /** {@inheritDoc} */
  90. @Override
  91. public void processEvent(final ActionType type, final StringBuffer format,
  92. final Object... arguments) {
  93. saveActions();
  94. }
  95. }, CoreActionType.CLIENT_CLOSED);
  96. // Make sure we listen for the killswitch
  97. IdentityManager.getGlobalConfig().addChangeListener("actions", "killswitch",
  98. new ConfigChangeListener() {
  99. /** {@inheritDoc} */
  100. @Override
  101. public void configChanged(final String domain, final String key) {
  102. killSwitch = IdentityManager.getGlobalConfig().getOptionBool(
  103. "actions", "killswitch");
  104. }
  105. });
  106. }
  107. /**
  108. * Saves all actions.
  109. */
  110. public static void saveActions() {
  111. for (ActionGroup group : groups.values()) {
  112. for (Action action : group) {
  113. action.save();
  114. }
  115. }
  116. }
  117. /**
  118. * Registers the specified default setting for actions.
  119. *
  120. * @param name The name of the setting to be registered
  121. * @param value The default value for the setting
  122. */
  123. public static void registerDefault(final String name, final String value) {
  124. IdentityManager.getAddonIdentity().setOption("actions", name, value);
  125. }
  126. /**
  127. * Registers the specified group of actions with the manager.
  128. *
  129. * @param group The group of actions to be registered
  130. */
  131. public static void registerGroup(final ActionGroup group) {
  132. groups.put(group.getName(), group);
  133. }
  134. /**
  135. * Registers a set of actiontypes with the manager.
  136. *
  137. * @param types An array of ActionTypes to be registered
  138. */
  139. @Precondition("None of the specified ActionTypes are null")
  140. public static void registerActionTypes(final ActionType[] types) {
  141. for (ActionType type : types) {
  142. Logger.assertTrue(type != null);
  143. if(!actionTypes.contains(type)) {
  144. actionTypes.add(type);
  145. actionTypeGroups.add(type.getType().getGroup(), type);
  146. }
  147. }
  148. }
  149. /**
  150. * Registers a set of action components with the manager.
  151. *
  152. * @param comps An array of ActionComponents to be registered
  153. */
  154. @Precondition("None of the specified ActionComponents are null")
  155. public static void registerActionComponents(final ActionComponent[] comps) {
  156. for (ActionComponent comp : comps) {
  157. Logger.assertTrue(comp != null);
  158. actionComponents.add(comp);
  159. }
  160. }
  161. /**
  162. * Registers a set of action comparisons with the manager.
  163. *
  164. * @param comps An array of ActionComparisons to be registered
  165. */
  166. @Precondition("None of the specified ActionComparisons are null")
  167. public static void registerActionComparisons(final ActionComparison[] comps) {
  168. for (ActionComparison comp : comps) {
  169. Logger.assertTrue(comp != null);
  170. actionComparisons.add(comp);
  171. }
  172. }
  173. /**
  174. * Returns a map of groups to action lists.
  175. *
  176. * @return a map of groups to action lists
  177. */
  178. public static Map<String, ActionGroup> getGroups() {
  179. return groups;
  180. }
  181. /**
  182. * Returns a map of type groups to types.
  183. *
  184. * @return A map of type groups to types
  185. */
  186. public static MapList<String, ActionType> getTypeGroups() {
  187. return actionTypeGroups;
  188. }
  189. /**
  190. * Loads actions from the user's directory.
  191. */
  192. public static void loadActions() {
  193. actions.clear();
  194. for (ActionGroup group : groups.values()) {
  195. group.clear();
  196. }
  197. final File dir = new File(getDirectory());
  198. if (!dir.exists()) {
  199. try {
  200. dir.mkdirs();
  201. dir.createNewFile();
  202. } catch (IOException ex) {
  203. Logger.userError(ErrorLevel.HIGH, "I/O error when creating actions directory: "
  204. + ex.getMessage());
  205. }
  206. }
  207. if (dir.listFiles() == null) {
  208. Logger.userError(ErrorLevel.MEDIUM, "Unable to load user action files");
  209. } else {
  210. for (File file : dir.listFiles()) {
  211. if (file.isDirectory()) {
  212. loadActions(file);
  213. }
  214. }
  215. }
  216. registerComponents();
  217. }
  218. /**
  219. * Creates new ActionGroupComponents for each action group.
  220. */
  221. private static void registerComponents() {
  222. for (ActionGroup group : groups.values()) {
  223. new ActionGroupComponent(group);
  224. }
  225. }
  226. /**
  227. * Loads action files from a specified group directory.
  228. *
  229. * @param dir The directory to scan.
  230. */
  231. @Precondition("The specified File is not null and represents a directory")
  232. private static void loadActions(final File dir) {
  233. Logger.assertTrue(dir != null);
  234. Logger.assertTrue(dir.isDirectory());
  235. if (!groups.containsKey(dir.getName())) {
  236. groups.put(dir.getName(), new ActionGroup(dir.getName()));
  237. }
  238. for (File file : dir.listFiles()) {
  239. new Action(dir.getName(), file.getName());
  240. }
  241. }
  242. /**
  243. * Registers an action with the manager.
  244. *
  245. * @param action The action to be registered
  246. */
  247. @Precondition("The specified action is not null")
  248. public static void registerAction(final Action action) {
  249. Logger.assertTrue(action != null);
  250. for (ActionType trigger : action.getTriggers()) {
  251. actions.add(trigger, action);
  252. }
  253. getGroup(action.getGroup()).add(action);
  254. }
  255. /**
  256. * Retrieves the action group with the specified name. A new group is
  257. * created if it doesn't already exist.
  258. *
  259. * @param name The name of the group to retrieve
  260. * @return The corresponding ActionGroup
  261. */
  262. public static ActionGroup getGroup(final String name) {
  263. if (!groups.containsKey(name)) {
  264. groups.put(name, new ActionGroup(name));
  265. }
  266. return groups.get(name);
  267. }
  268. /**
  269. * Unregisters an action with the manager.
  270. *
  271. * @param action The action to be unregistered
  272. */
  273. @Precondition("The specified action is not null")
  274. public static void unregisterAction(final Action action) {
  275. Logger.assertTrue(action != null);
  276. actions.removeFromAll(action);
  277. getGroup(action.getGroup()).remove(action);
  278. }
  279. /**
  280. * Reregisters the specified action. Should be used when the action's
  281. * triggers change.
  282. *
  283. * @param action The action to be reregistered
  284. */
  285. public static void reregisterAction(final Action action) {
  286. unregisterAction(action);
  287. registerAction(action);
  288. }
  289. /**
  290. * Deletes the specified action.
  291. *
  292. * @param action The action to be deleted
  293. */
  294. @Precondition("The specified Action is not null")
  295. public static void deleteAction(final Action action) {
  296. Logger.assertTrue(action != null);
  297. unregisterAction(action);
  298. action.delete();
  299. }
  300. /**
  301. * Processes an event of the specified type.
  302. *
  303. * @param type The type of the event to process
  304. * @param format The format of the message that's going to be displayed for
  305. * the event. Actions may change this format.
  306. * @param arguments The arguments for the event
  307. */
  308. @Precondition({
  309. "The specified ActionType is not null",
  310. "The specified ActionType has a valid ActionMetaType",
  311. "The length of the arguments array equals the arity of the ActionType's ActionMetaType"
  312. })
  313. public static void processEvent(final ActionType type,
  314. final StringBuffer format, final Object ... arguments) {
  315. Logger.assertTrue(type != null);
  316. Logger.assertTrue(type.getType() != null);
  317. Logger.assertTrue(type.getType().getArity() == arguments.length);
  318. if (listeners.containsKey(type)) {
  319. for (ActionListener listener :
  320. new ArrayList<ActionListener>(listeners.get(type))) {
  321. try {
  322. listener.processEvent(type, format, arguments);
  323. } catch (Exception e) {
  324. Logger.userError(ErrorLevel.MEDIUM, "Error processing action: "
  325. + e.getMessage(), e);
  326. }
  327. }
  328. }
  329. if (!killSwitch) {
  330. triggerActions(type, format, arguments);
  331. }
  332. }
  333. /**
  334. * Triggers actions that respond to the specified type.
  335. *
  336. * @param type The type of the event to process
  337. * @param format The format of the message that's going to be displayed for
  338. * the event. Actions may change this format.*
  339. * @param arguments The arguments for the event
  340. */
  341. @Precondition("The specified ActionType is not null")
  342. private static void triggerActions(final ActionType type,
  343. final StringBuffer format, final Object ... arguments) {
  344. Logger.assertTrue(type != null);
  345. if (actions.containsKey(type)) {
  346. for (Action action : new ArrayList<Action>(actions.get(type))) {
  347. final String actionName = (action.getGroup() + "/" +
  348. action.getName()).replace(' ', '.');
  349. final boolean hasOption = IdentityManager.getGlobalConfig()
  350. .hasOptionBool("disable_action", actionName);
  351. final boolean disabled = (hasOption) ? IdentityManager
  352. .getGlobalConfig().getOptionBool("disable_action",
  353. actionName) : false;
  354. if (!disabled) {
  355. try {
  356. action.trigger(format, arguments);
  357. } catch (NoSuchMethodError e) {
  358. Logger.appError(ErrorLevel.MEDIUM, "Error processing action: "
  359. + e.getMessage(), e);
  360. } catch (Exception e) {
  361. Logger.appError(ErrorLevel.MEDIUM, "Error processing action: "
  362. + e.getMessage(), e);
  363. }
  364. }
  365. }
  366. }
  367. }
  368. /**
  369. * Returns the directory that should be used to store actions.
  370. *
  371. * @return The directory that should be used to store actions
  372. */
  373. public static String getDirectory() {
  374. return Main.getConfigDir() + "actions" + System.getProperty("file.separator");
  375. }
  376. /**
  377. * Creates a new group with the specified name.
  378. *
  379. * @param group The group to be created
  380. *
  381. * @return The newly created group
  382. */
  383. @Precondition({
  384. "The specified group is non-null and not empty",
  385. "The specified group is not an existing group"
  386. })
  387. public static ActionGroup makeGroup(final String group) {
  388. Logger.assertTrue(group != null);
  389. Logger.assertTrue(!group.isEmpty());
  390. Logger.assertTrue(!groups.containsKey(group));
  391. final File file = new File(getDirectory() + group);
  392. if (file.isDirectory() || file.mkdir()) {
  393. final ActionGroup actionGroup = new ActionGroup(group);
  394. groups.put(group, actionGroup);
  395. return actionGroup;
  396. } else {
  397. throw new IllegalArgumentException("Unable to create action group directory"
  398. + "\n\nDir: " + getDirectory() + group);
  399. }
  400. }
  401. /**
  402. * Removes the group with the specified name.
  403. *
  404. * @param group The group to be removed
  405. */
  406. @Precondition({
  407. "The specified group is non-null and not empty",
  408. "The specified group is an existing group"
  409. })
  410. public static void removeGroup(final String group) {
  411. Logger.assertTrue(group != null);
  412. Logger.assertTrue(!group.isEmpty());
  413. Logger.assertTrue(groups.containsKey(group));
  414. for (Action action : groups.get(group).getActions()) {
  415. unregisterAction(action);
  416. }
  417. final File dir = new File(getDirectory() + group);
  418. if (dir.isDirectory()) {
  419. for (File file : dir.listFiles()) {
  420. if (!file.delete()) {
  421. Logger.userError(ErrorLevel.MEDIUM, "Unable to remove file: "
  422. + file.getAbsolutePath());
  423. return;
  424. }
  425. }
  426. }
  427. if (!dir.delete()) {
  428. Logger.userError(ErrorLevel.MEDIUM, "Unable to remove directory: "
  429. + dir.getAbsolutePath());
  430. return;
  431. }
  432. groups.remove(group);
  433. }
  434. /**
  435. * Renames the specified group.
  436. *
  437. * @param oldName The old name of the group
  438. * @param newName The new name of the group
  439. */
  440. @Precondition({
  441. "The old name is non-null and not empty",
  442. "The old name is an existing group",
  443. "The new name is non-null and not empty",
  444. "The new name is not an existing group",
  445. "The old name does not equal the new name"
  446. })
  447. public static void renameGroup(final String oldName, final String newName) {
  448. Logger.assertTrue(oldName != null);
  449. Logger.assertTrue(!oldName.isEmpty());
  450. Logger.assertTrue(newName != null);
  451. Logger.assertTrue(!newName.isEmpty());
  452. Logger.assertTrue(groups.containsKey(oldName));
  453. Logger.assertTrue(!groups.containsKey(newName));
  454. Logger.assertTrue(!newName.equals(oldName));
  455. makeGroup(newName);
  456. for (Action action : groups.get(oldName).getActions()) {
  457. action.setGroup(newName);
  458. getGroup(oldName).remove(action);
  459. getGroup(newName).add(action);
  460. }
  461. removeGroup(oldName);
  462. }
  463. /**
  464. * Returns the action comparison specified by the given string, or null if it
  465. * doesn't match a valid registered action comparison.
  466. *
  467. * @param type The name of the action comparison to try and find
  468. * @return The actioncomparison with the specified name, or null on failure
  469. */
  470. public static ActionType getActionType(final String type) {
  471. if (type == null || type.isEmpty()) {
  472. return null;
  473. }
  474. for (ActionType target : actionTypes) {
  475. if (((Enum) target).name().equals(type)) {
  476. return target;
  477. }
  478. }
  479. return null;
  480. }
  481. /**
  482. * Returns a list of action types that are compatible with the one
  483. * specified.
  484. *
  485. * @param type The type to be checked against
  486. * @return A list of compatible action types
  487. */
  488. @Precondition("The specified type is not null")
  489. public static List<ActionType> getCompatibleTypes(final ActionType type) {
  490. Logger.assertTrue(type != null);
  491. final List<ActionType> res = new ArrayList<ActionType>();
  492. for (ActionType target : actionTypes) {
  493. if (!target.equals(type) && target.getType().equals(type.getType())) {
  494. res.add(target);
  495. }
  496. }
  497. return res;
  498. }
  499. /**
  500. * Returns a list of action components that are compatible with the
  501. * specified class.
  502. *
  503. * @param target The class to be tested
  504. * @return A list of compatible action components
  505. */
  506. @Precondition("The specified target is not null")
  507. public static List<ActionComponent> getCompatibleComponents(final Class target) {
  508. Logger.assertTrue(target != null);
  509. final List<ActionComponent> res = new ArrayList<ActionComponent>();
  510. for (ActionComponent subject : actionComponents) {
  511. if (subject.appliesTo().equals(target)) {
  512. res.add(subject);
  513. }
  514. }
  515. return res;
  516. }
  517. /**
  518. * Returns a list of action comparisons that are compatible with the
  519. * specified class.
  520. *
  521. * @param target The class to be tested
  522. * @return A list of compatible action comparisons
  523. */
  524. @Precondition("The specified target is not null")
  525. public static List<ActionComparison> getCompatibleComparisons(final Class target) {
  526. Logger.assertTrue(target != null);
  527. final List<ActionComparison> res = new ArrayList<ActionComparison>();
  528. for (ActionComparison subject : actionComparisons) {
  529. if (subject.appliesTo().equals(target)) {
  530. res.add(subject);
  531. }
  532. }
  533. return res;
  534. }
  535. /**
  536. * Returns a list of all the action types registered by this manager.
  537. *
  538. * @return A list of registered action types
  539. */
  540. public static List<ActionType> getTypes() {
  541. return actionTypes;
  542. }
  543. /**
  544. * Returns a list of all the action types registered by this manager.
  545. *
  546. * @return A list of registered action comparisons
  547. */
  548. public static List<ActionComparison> getComparisons() {
  549. return actionComparisons;
  550. }
  551. /**
  552. * Returns the action component specified by the given string, or null if it
  553. * doesn't match a valid registered action component.
  554. *
  555. * @param type The name of the action component to try and find
  556. * @return The actioncomponent with the specified name, or null on failure
  557. */
  558. @Precondition("The specified type is non-null and not empty")
  559. public static ActionComponent getActionComponent(final String type) {
  560. Logger.assertTrue(type != null);
  561. Logger.assertTrue(!type.isEmpty());
  562. for (ActionComponent target : actionComponents) {
  563. if (((Enum) target).name().equals(type)) {
  564. return target;
  565. }
  566. }
  567. return null;
  568. }
  569. /**
  570. * Returns the action type specified by the given string, or null if it
  571. * doesn't match a valid registered action type.
  572. *
  573. * @param type The name of the action type to try and find
  574. * @return The actiontype with the specified name, or null on failure
  575. */
  576. @Precondition("The specified type is non-null and not empty")
  577. public static ActionComparison getActionComparison(final String type) {
  578. Logger.assertTrue(type != null);
  579. Logger.assertTrue(!type.isEmpty());
  580. for (ActionComparison target : actionComparisons) {
  581. if (((Enum) target).name().equals(type)) {
  582. return target;
  583. }
  584. }
  585. return null;
  586. }
  587. /**
  588. * Installs an action pack located at the specified path.
  589. *
  590. * @param path The full path of the action pack .zip.
  591. * @throws IOException If the zip cannot be extracted
  592. */
  593. public static void installActionPack(final String path) throws IOException {
  594. final ZipResourceManager ziprm = ZipResourceManager.getInstance(path);
  595. ziprm.extractResources("", getDirectory());
  596. loadActions();
  597. new File(path).delete();
  598. }
  599. /**
  600. * Adds a new listener for the specified action type.
  601. *
  602. * @param types The action types that are to be listened for
  603. * @param listener The listener to be added
  604. */
  605. public static void addListener(final ActionListener listener, final ActionType ... types) {
  606. for (ActionType type : types) {
  607. listeners.add(type, listener);
  608. }
  609. }
  610. /**
  611. * Removes a listener for the specified action type.
  612. *
  613. * @param types The action types that were being listened for
  614. * @param listener The listener to be removed
  615. */
  616. public static void removeListener(final ActionListener listener, final ActionType ... types) {
  617. for (ActionType type : types) {
  618. listeners.remove(type, listener);
  619. }
  620. }
  621. /**
  622. * Removes a listener for all action types.
  623. *
  624. * @param listener The listener to be removed
  625. */
  626. public static void removeListener(final ActionListener listener) {
  627. listeners.removeFromAll(listener);
  628. }
  629. }