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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  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.parser.xmpp;
  23. import com.dmdirc.parser.common.AwayState;
  24. import com.dmdirc.parser.common.BaseSocketAwareParser;
  25. import com.dmdirc.parser.common.ChannelJoinRequest;
  26. import com.dmdirc.parser.common.ChildImplementations;
  27. import com.dmdirc.parser.common.CompositionState;
  28. import com.dmdirc.parser.common.DefaultStringConverter;
  29. import com.dmdirc.parser.common.ParserError;
  30. import com.dmdirc.parser.common.QueuePriority;
  31. import com.dmdirc.parser.interfaces.ChannelInfo;
  32. import com.dmdirc.parser.interfaces.ClientInfo;
  33. import com.dmdirc.parser.interfaces.LocalClientInfo;
  34. import com.dmdirc.parser.interfaces.StringConverter;
  35. import com.dmdirc.parser.interfaces.callbacks.AwayStateListener;
  36. import com.dmdirc.parser.interfaces.callbacks.CallbackInterface;
  37. import com.dmdirc.parser.interfaces.callbacks.ChannelSelfJoinListener;
  38. import com.dmdirc.parser.interfaces.callbacks.CompositionStateChangeListener;
  39. import com.dmdirc.parser.interfaces.callbacks.ConnectErrorListener;
  40. import com.dmdirc.parser.interfaces.callbacks.DataInListener;
  41. import com.dmdirc.parser.interfaces.callbacks.DataOutListener;
  42. import com.dmdirc.parser.interfaces.callbacks.NumericListener;
  43. import com.dmdirc.parser.interfaces.callbacks.OtherAwayStateListener;
  44. import com.dmdirc.parser.interfaces.callbacks.PrivateActionListener;
  45. import com.dmdirc.parser.interfaces.callbacks.PrivateMessageListener;
  46. import com.dmdirc.parser.interfaces.callbacks.ServerReadyListener;
  47. import com.dmdirc.parser.interfaces.callbacks.SocketCloseListener;
  48. import java.net.URI;
  49. import java.util.Collection;
  50. import java.util.Collections;
  51. import java.util.Date;
  52. import java.util.HashMap;
  53. import java.util.List;
  54. import java.util.Map;
  55. import java.util.regex.Matcher;
  56. import java.util.regex.Pattern;
  57. import org.jivesoftware.smack.Chat;
  58. import org.jivesoftware.smack.ChatManagerListener;
  59. import org.jivesoftware.smack.ConnectionConfiguration;
  60. import org.jivesoftware.smack.ConnectionListener;
  61. import org.jivesoftware.smack.PacketListener;
  62. import org.jivesoftware.smack.RosterEntry;
  63. import org.jivesoftware.smack.RosterListener;
  64. import org.jivesoftware.smack.XMPPConnection;
  65. import org.jivesoftware.smack.XMPPException;
  66. import org.jivesoftware.smack.filter.PacketFilter;
  67. import org.jivesoftware.smack.packet.Message;
  68. import org.jivesoftware.smack.packet.Packet;
  69. import org.jivesoftware.smack.packet.Presence;
  70. import org.jivesoftware.smackx.ChatState;
  71. import org.jivesoftware.smackx.ChatStateListener;
  72. import org.jivesoftware.smackx.ChatStateManager;
  73. import org.jivesoftware.smackx.muc.MultiUserChat;
  74. import org.slf4j.LoggerFactory;
  75. /**
  76. * A parser which can understand the XMPP protocol.
  77. */
  78. @ChildImplementations({
  79. XmppClientInfo.class, XmppLocalClientInfo.class, XmppFakeChannel.class,
  80. XmppChannelClientInfo.class
  81. })
  82. public class XmppParser extends BaseSocketAwareParser {
  83. private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(XmppParser.class);
  84. /** Pattern to use to extract priority. */
  85. private static final Pattern PRIORITY_PATTERN = Pattern.compile(
  86. "(?i)(?:^|&)priority=([0-9]+)(?:$|&)");
  87. /** The connection to use. */
  88. private XMPPConnection connection;
  89. /** The state manager for the current connection. */
  90. private ChatStateManager stateManager;
  91. /** A cache of known chats. */
  92. private final Map<String, Chat> chats = new HashMap<>();
  93. /** A cache of known clients. */
  94. private final Map<String, XmppClientInfo> contacts = new HashMap<>();
  95. /** Whether or not to use a fake local channel for a buddy list replacement. */
  96. private final boolean useFakeChannel;
  97. /** The priority of this endpoint. */
  98. private final int priority;
  99. /** The fake channel to use is useFakeChannel is enabled. */
  100. private XmppFakeChannel fakeChannel;
  101. /**
  102. * Creates a new XMPP parser for the specified address.
  103. *
  104. * @param address The address to connect to
  105. */
  106. public XmppParser(final URI address) {
  107. super(address);
  108. if (address.getQuery() == null) {
  109. useFakeChannel = false;
  110. priority = 0;
  111. } else {
  112. final Matcher matcher = PRIORITY_PATTERN.matcher(address.getQuery());
  113. useFakeChannel = address.getQuery().matches("(?i).*(^|&)showchannel($|&).*");
  114. priority = matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
  115. }
  116. LOG.debug(
  117. "XMPP parser created with query string {}, parsed fake channel = {}, priority = {}",
  118. address.getQuery(), useFakeChannel, priority);
  119. }
  120. @Override
  121. public void disconnect(final String message) {
  122. super.disconnect(message);
  123. // TODO: Pass quit message on as presence?
  124. connection.disconnect();
  125. }
  126. @Override
  127. public void joinChannels(final ChannelJoinRequest... channels) {
  128. for (ChannelJoinRequest request : channels) {
  129. final MultiUserChat muc = new MultiUserChat(connection, request.getName().substring(1));
  130. try {
  131. if (request.getPassword() == null) {
  132. muc.join(getLocalClient().getNickname());
  133. } else {
  134. muc.join(getLocalClient().getNickname(), request.getPassword());
  135. }
  136. // TODO: Send callbacks etc
  137. } catch (XMPPException ex) {
  138. // TODO: handle
  139. }
  140. }
  141. }
  142. @Override
  143. public ChannelInfo getChannel(final String channel) {
  144. // TODO: Implement
  145. throw new UnsupportedOperationException("Not supported yet.");
  146. }
  147. @Override
  148. public Collection<? extends ChannelInfo> getChannels() {
  149. return Collections.<ChannelInfo>emptyList();
  150. }
  151. @Override
  152. public int getMaxLength(final String type, final String target) {
  153. return Integer.MAX_VALUE;
  154. }
  155. @Override
  156. public int getMaxLength() {
  157. return Integer.MAX_VALUE;
  158. }
  159. @Override
  160. public LocalClientInfo getLocalClient() {
  161. final String[] parts = parseHostmask(connection.getUser());
  162. // TODO: Cache this
  163. return new XmppLocalClientInfo(this, parts[0], parts[2], parts[1]);
  164. }
  165. @Override
  166. public XmppClientInfo getClient(final String details) {
  167. final String[] parts = parseHostmask(details);
  168. if (!contacts.containsKey(parts[0])) {
  169. contacts.put(parts[0], new XmppClientInfo(this, parts[0], parts[2], parts[1]));
  170. }
  171. return contacts.get(parts[0]);
  172. }
  173. @Override
  174. public void sendRawMessage(final String message) {
  175. // Urgh, hacky horrible rubbish. These commands should call methods.
  176. if (message.toUpperCase().startsWith("WHOIS ")) {
  177. handleWhois(message.split(" ")[1]);
  178. } else if (!message.isEmpty() && message.charAt(0) == '<') {
  179. // Looks vaguely like XML, let's send it.
  180. connection.sendPacket(new Packet() {
  181. @Override
  182. public String toXML() {
  183. return message;
  184. }
  185. });
  186. }
  187. }
  188. /**
  189. * Handles a whois request for the specified target.
  190. *
  191. * @param target The user being WHOIS'd
  192. */
  193. private void handleWhois(final String target) {
  194. // Urgh, hacky horrible rubbish. This should be abstracted.
  195. if (contacts.containsKey(target)) {
  196. final XmppClientInfo client = contacts.get(target);
  197. final String[] userParts = client.getNickname().split("@", 2);
  198. callNumericCallback(311, target, userParts[0], userParts[1], "*", client.getRealname());
  199. for (Map.Entry<String, XmppEndpoint> endpoint : client.getEndpoints().entrySet()) {
  200. callNumericCallback(399, target, endpoint.getKey(),
  201. "(" + endpoint.getValue().getPresence() + ")", "has endpoint");
  202. }
  203. } else {
  204. callNumericCallback(401, target, "No such contact found");
  205. }
  206. callNumericCallback(318, target, "End of /WHOIS.");
  207. }
  208. private void callNumericCallback(final int numeric, final String... args) {
  209. final String[] newArgs = new String[args.length + 3];
  210. newArgs[0] = ":xmpp.server";
  211. newArgs[1] = (numeric < 100 ? "0" : "") + (numeric < 10 ? "0" : "") + numeric;
  212. newArgs[2] = getLocalClient().getNickname();
  213. System.arraycopy(args, 0, newArgs, 3, args.length);
  214. getCallback(NumericListener.class).onNumeric(this, new Date(), numeric, newArgs);
  215. }
  216. @Override
  217. public void sendRawMessage(final String message, final QueuePriority priority) {
  218. sendRawMessage(message);
  219. }
  220. @Override
  221. public StringConverter getStringConverter() {
  222. return new DefaultStringConverter();
  223. }
  224. @Override
  225. public boolean isValidChannelName(final String name) {
  226. return false; // TODO: Implement
  227. }
  228. @Override
  229. public boolean compareURI(final URI uri) {
  230. throw new UnsupportedOperationException("Not supported yet.");
  231. }
  232. @Override
  233. public Collection<? extends ChannelJoinRequest> extractChannels(final URI uri) {
  234. return Collections.<ChannelJoinRequest>emptyList();
  235. }
  236. @Override
  237. public String getNetworkName() {
  238. return "XMPP"; // TODO
  239. }
  240. @Override
  241. public String getServerSoftware() {
  242. return "Unknown"; // TODO
  243. }
  244. @Override
  245. public String getServerSoftwareType() {
  246. return "XMPP"; // TODO
  247. }
  248. @Override
  249. public List<String> getServerInformationLines() {
  250. return Collections.emptyList(); // TODO
  251. }
  252. @Override
  253. public int getMaxTopicLength() {
  254. return 0; // TODO
  255. }
  256. @Override
  257. public String getBooleanChannelModes() {
  258. return ""; // TODO
  259. }
  260. @Override
  261. public String getListChannelModes() {
  262. return ""; // TODO
  263. }
  264. @Override
  265. public int getMaxListModes(final char mode) {
  266. return 0; // TODO
  267. }
  268. @Override
  269. public boolean isUserSettable(final char mode) {
  270. throw new UnsupportedOperationException("Not supported yet.");
  271. }
  272. @Override
  273. public String getParameterChannelModes() {
  274. return ""; // TODO
  275. }
  276. @Override
  277. public String getDoubleParameterChannelModes() {
  278. return ""; // TODO
  279. }
  280. @Override
  281. public String getUserModes() {
  282. return ""; // TODO
  283. }
  284. @Override
  285. public String getChannelUserModes() {
  286. return ""; // TODO
  287. }
  288. @Override
  289. public String getChannelPrefixes() {
  290. return "#";
  291. }
  292. @Override
  293. public long getServerLatency() {
  294. return 1000L; // TODO
  295. }
  296. @Override
  297. public void sendCTCP(final String target, final String type, final String message) {
  298. throw new UnsupportedOperationException("Not supported yet.");
  299. }
  300. @Override
  301. public void sendCTCPReply(final String target, final String type, final String message) {
  302. throw new UnsupportedOperationException("Not supported yet.");
  303. }
  304. @Override
  305. public void sendMessage(final String target, final String message) {
  306. if (!chats.containsKey(target)) {
  307. LOG.debug("Creating new chat for {}", target);
  308. chats.put(target, connection.getChatManager().createChat(target,
  309. new MessageListenerImpl()));
  310. }
  311. try {
  312. chats.get(target).sendMessage(message);
  313. } catch (XMPPException ex) {
  314. // TODO: Handle this
  315. }
  316. }
  317. @Override
  318. public void sendNotice(final String target, final String message) {
  319. throw new UnsupportedOperationException("Not supported yet.");
  320. }
  321. @Override
  322. public void sendAction(final String target, final String message) {
  323. sendMessage(target, "/me " + message);
  324. }
  325. @Override
  326. public void sendInvite(final String channel, final String user) {
  327. throw new UnsupportedOperationException("Not supported yet.");
  328. }
  329. @Override
  330. public void sendWhois(final String nickname) {
  331. // TODO: Implement this
  332. }
  333. @Override
  334. public String getLastLine() {
  335. return "TODO: Implement me";
  336. }
  337. @Override
  338. public String[] parseHostmask(final String hostmask) {
  339. return new XmppProtocolDescription().parseHostmask(hostmask);
  340. }
  341. @Override
  342. public long getPingTime() {
  343. throw new UnsupportedOperationException("Not supported yet.");
  344. }
  345. @Override
  346. public void run() {
  347. if (getURI().getUserInfo() == null || !getURI().getUserInfo().contains(":")) {
  348. getCallback(ConnectErrorListener.class).onConnectError(this,
  349. new Date(), new ParserError(ParserError.ERROR_USER,
  350. "User name and password must be specified in URI", ""));
  351. return;
  352. }
  353. final String[] userInfoParts = getURI().getUserInfo().split(":", 2);
  354. final String[] userParts = userInfoParts[0].split("@", 2);
  355. final ConnectionConfiguration config = new ConnectionConfiguration(getURI().getHost(),
  356. getURI().getPort(), userParts[0]);
  357. config.setSecurityMode(getURI().getScheme().equalsIgnoreCase("xmpps")
  358. ? ConnectionConfiguration.SecurityMode.required
  359. : ConnectionConfiguration.SecurityMode.disabled);
  360. config.setSASLAuthenticationEnabled(true);
  361. config.setReconnectionAllowed(false);
  362. config.setRosterLoadedAtLogin(true);
  363. config.setSocketFactory(getSocketFactory());
  364. connection = new FixedXmppConnection(config);
  365. try {
  366. connection.connect();
  367. connection.addConnectionListener(new ConnectionListenerImpl());
  368. connection.addPacketListener(new PacketListenerImpl(DataInListener.class),
  369. new AcceptAllPacketFilter());
  370. connection.addPacketSendingListener(new PacketListenerImpl(DataOutListener.class),
  371. new AcceptAllPacketFilter());
  372. connection.getChatManager().addChatListener(new ChatManagerListenerImpl());
  373. try {
  374. connection.login(userInfoParts[0], userInfoParts[1], "DMDirc.");
  375. } catch (XMPPException ex) {
  376. getCallback(ConnectErrorListener.class).onConnectError(this,
  377. new Date(), new ParserError(ParserError.ERROR_USER,
  378. ex.getMessage(), ""));
  379. return;
  380. }
  381. connection.sendPacket(new Presence(Presence.Type.available, null, priority,
  382. Presence.Mode.available));
  383. connection.getRoster().addRosterListener(new RosterListenerImpl());
  384. stateManager = ChatStateManager.getInstance(connection);
  385. setServerName(connection.getServiceName());
  386. getCallback(ServerReadyListener.class).onServerReady(this, new Date());
  387. for (RosterEntry contact : connection.getRoster().getEntries()) {
  388. getClient(contact.getUser()).setRosterEntry(contact);
  389. }
  390. if (useFakeChannel) {
  391. fakeChannel = new XmppFakeChannel(this, "&contacts");
  392. getCallback(ChannelSelfJoinListener.class).
  393. onChannelSelfJoin(null, null, fakeChannel);
  394. fakeChannel.updateContacts(contacts.values());
  395. contacts.values().stream().filter(XmppClientInfo::isAway).forEach(client ->
  396. getCallback(OtherAwayStateListener.class).onAwayStateOther(this, new Date(),
  397. client, AwayState.UNKNOWN, AwayState.AWAY));
  398. }
  399. } catch (XMPPException ex) {
  400. LOG.debug("Go an XMPP exception", ex);
  401. connection = null;
  402. final ParserError error = new ParserError(ParserError.ERROR_ERROR, "Unable to connect",
  403. "");
  404. if (ex.getWrappedThrowable() instanceof Exception) {
  405. // Pass along the underlying exception instead of an XMPP
  406. // specific one
  407. error.setException((Exception) ex.getWrappedThrowable());
  408. } else {
  409. error.setException(ex);
  410. }
  411. getCallback(ConnectErrorListener.class).onConnectError(this, new Date(), error);
  412. }
  413. }
  414. /**
  415. * Handles a client's away state changing.
  416. *
  417. * @param client The client whose state is changing
  418. * @param isBack True if the client is coming back, false if they're going away
  419. */
  420. public void handleAwayStateChange(final ClientInfo client, final boolean isBack) {
  421. LOG.debug("Handling away state change for {} to {}", client.getNickname(), isBack);
  422. if (useFakeChannel) {
  423. getCallback(OtherAwayStateListener.class)
  424. .onAwayStateOther(null, null, client,
  425. isBack ? AwayState.AWAY : AwayState.HERE,
  426. isBack ? AwayState.HERE : AwayState.AWAY);
  427. }
  428. }
  429. /**
  430. * Marks the local user as away with the specified reason.
  431. *
  432. * @param reason The away reason
  433. */
  434. public void setAway(final String reason) {
  435. connection.sendPacket(new Presence(Presence.Type.available, reason,
  436. priority, Presence.Mode.away));
  437. getCallback(AwayStateListener.class).onAwayState(this, new Date(),
  438. AwayState.HERE, AwayState.AWAY, reason);
  439. }
  440. /**
  441. * Marks the local user as back.
  442. */
  443. public void setBack() {
  444. connection.sendPacket(new Presence(Presence.Type.available, null,
  445. priority, Presence.Mode.available));
  446. getCallback(AwayStateListener.class).onAwayState(this, new Date(),
  447. AwayState.AWAY, AwayState.HERE, null);
  448. }
  449. @Override
  450. public void setCompositionState(final String host, final CompositionState state) {
  451. LOG.debug("Setting composition state for {} to {}", host, state);
  452. final Chat chat = chats.get(parseHostmask(host)[0]);
  453. final ChatState newState;
  454. switch (state) {
  455. case ENTERED_TEXT:
  456. newState = ChatState.paused;
  457. break;
  458. case TYPING:
  459. newState = ChatState.composing;
  460. break;
  461. case IDLE:
  462. default:
  463. newState = ChatState.active;
  464. break;
  465. }
  466. if (chat != null && stateManager != null) {
  467. try {
  468. stateManager.setCurrentState(newState, chat);
  469. } catch (XMPPException ex) {
  470. // Can't set chat state... Oh well?
  471. LOG.info("Couldn't set composition state", ex);
  472. }
  473. }
  474. }
  475. @Override
  476. public void requestGroupList(final String searchTerms) {
  477. // Do nothing
  478. }
  479. private class ConnectionListenerImpl implements ConnectionListener {
  480. @Override
  481. public void connectionClosed() {
  482. getCallback(SocketCloseListener.class).onSocketClosed(XmppParser.this, new Date());
  483. }
  484. @Override
  485. public void connectionClosedOnError(final Exception excptn) {
  486. // TODO: Handle exception
  487. getCallback(SocketCloseListener.class).onSocketClosed(XmppParser.this, new Date());
  488. }
  489. @Override
  490. public void reconnectingIn(final int i) {
  491. throw new UnsupportedOperationException("Not supported yet.");
  492. }
  493. @Override
  494. public void reconnectionSuccessful() {
  495. throw new UnsupportedOperationException("Not supported yet.");
  496. }
  497. @Override
  498. public void reconnectionFailed(final Exception excptn) {
  499. throw new UnsupportedOperationException("Not supported yet.");
  500. }
  501. }
  502. private class RosterListenerImpl implements RosterListener {
  503. @Override
  504. public void entriesAdded(final Collection<String> clctn) {
  505. // Do nothing, yet
  506. }
  507. @Override
  508. public void entriesUpdated(final Collection<String> clctn) {
  509. // Do nothing, yet
  510. }
  511. @Override
  512. public void entriesDeleted(final Collection<String> clctn) {
  513. // Do nothing, yet
  514. }
  515. @Override
  516. public void presenceChanged(final Presence prsnc) {
  517. getClient(prsnc.getFrom()).setPresence(prsnc);
  518. }
  519. }
  520. private class ChatManagerListenerImpl implements ChatManagerListener {
  521. @Override
  522. public void chatCreated(final Chat chat, final boolean bln) {
  523. if (!bln) {
  524. // Only add chats that weren't created locally
  525. chats.put(parseHostmask(chat.getParticipant())[0], chat);
  526. chat.addMessageListener(new MessageListenerImpl());
  527. }
  528. }
  529. }
  530. private class MessageListenerImpl implements ChatStateListener {
  531. @Override
  532. public void processMessage(final Chat chat, final Message msg) {
  533. if (msg.getType() == Message.Type.error) {
  534. getCallback(NumericListener.class).onNumeric(XmppParser.this, new Date(),
  535. 404, new String[]{
  536. ":xmpp", "404", getLocalClient().getNickname(),
  537. msg.getFrom(),
  538. "Cannot send message: " + msg.getError().toString()
  539. });
  540. return;
  541. }
  542. if (msg.getBody() != null) {
  543. if (msg.getBody().startsWith("/me ")) {
  544. getCallback(PrivateActionListener.class).onPrivateAction(XmppParser.this,
  545. new Date(), msg.getBody().substring(4), msg.getFrom());
  546. } else {
  547. getCallback(PrivateMessageListener.class).onPrivateMessage(XmppParser.this,
  548. new Date(), msg.getBody(), msg.getFrom());
  549. }
  550. }
  551. }
  552. @Override
  553. public void stateChanged(final Chat chat, final ChatState cs) {
  554. final CompositionState state;
  555. switch (cs) {
  556. case paused:
  557. state = CompositionState.ENTERED_TEXT;
  558. break;
  559. case composing:
  560. state = CompositionState.TYPING;
  561. break;
  562. case active:
  563. case gone:
  564. case inactive:
  565. default:
  566. state = CompositionState.IDLE;
  567. break;
  568. }
  569. getCallback(CompositionStateChangeListener.class)
  570. .onCompositionStateChanged(XmppParser.this, new Date(), state,
  571. chat.getParticipant());
  572. }
  573. }
  574. private class PacketListenerImpl implements PacketListener {
  575. private final Class<? extends CallbackInterface> callback;
  576. public PacketListenerImpl(final Class<? extends CallbackInterface> callback) {
  577. this.callback = callback;
  578. }
  579. @Override
  580. public void processPacket(final Packet packet) {
  581. if (callback.equals(DataOutListener.class)) {
  582. getCallback(DataOutListener.class).onDataOut(XmppParser.this, new Date(),
  583. packet.toXML(), true);
  584. } else {
  585. getCallback(DataInListener.class).onDataIn(XmppParser.this, new Date(),
  586. packet.toXML());
  587. }
  588. }
  589. }
  590. private static class AcceptAllPacketFilter implements PacketFilter {
  591. @Override
  592. public boolean accept(final Packet packet) {
  593. return true;
  594. }
  595. }
  596. }