Bläddra i källkod

Initial work on XMPP parser and parser base abstraction

Depends-On: I3bfbb811a5b5055d8dbe511a0390c3413d133292
Depends-On: I694d18a653ca8c3e3cad9d0e766e2fd207e615e9
Change-Id: I4ff484e8a2597017283eea557170c4aeb2029faa
Reviewed-on: http://gerrit.dmdirc.com/1852
Automatic-Compile: DMDirc Local Commits <dmdirc@googlemail.com>
Reviewed-by: Greg Holmes <greg@dmdirc.com>
tags/0.7rc1
Chris Smith 13 år sedan
förälder
incheckning
ce075faf2f

Binär
lib/smack.jar Visa fil


Binär
lib/smackx.jar Visa fil


+ 12
- 4
src/com/dmdirc/addons/parser_twitter/Twitter.java Visa fil

@@ -62,7 +62,6 @@ import com.dmdirc.parser.interfaces.callbacks.MotdStartListener;
62 62
 import com.dmdirc.parser.interfaces.callbacks.NetworkDetectedListener;
63 63
 import com.dmdirc.parser.interfaces.callbacks.NickChangeListener;
64 64
 import com.dmdirc.parser.interfaces.callbacks.NumericListener;
65
-import com.dmdirc.parser.interfaces.callbacks.Post005Listener;
66 65
 import com.dmdirc.parser.interfaces.callbacks.PrivateMessageListener;
67 66
 import com.dmdirc.parser.interfaces.callbacks.PrivateNoticeListener;
68 67
 import com.dmdirc.parser.interfaces.callbacks.ServerReadyListener;
@@ -94,6 +93,16 @@ public class Twitter implements Parser, TwitterErrorHandler, TwitterRawHandler,
94 93
     private static final long PRUNE_COUNT = 20;
95 94
     /** Maximum age of items to leave in the status cache when pruning. */
96 95
     private static final long PRUNE_TIME = 3600 * 1000;
96
+    
97
+    /** A map of this parser's implementations of common interfaces. */
98
+    protected static final Map<Class<?>, Class<?>> IMPL_MAP = new HashMap<Class<?>, Class<?>>();
99
+
100
+    static {
101
+        IMPL_MAP.put(ChannelClientInfo.class, TwitterChannelClientInfo.class);
102
+        IMPL_MAP.put(ChannelInfo.class, TwitterChannelInfo.class);
103
+        IMPL_MAP.put(ClientInfo.class, TwitterClientInfo.class);
104
+        IMPL_MAP.put(LocalClientInfo.class, TwitterClientInfo.class);
105
+    }
97 106
 
98 107
     /** Are we connected? */
99 108
     private boolean connected = false;
@@ -122,7 +131,7 @@ public class Twitter implements Parser, TwitterErrorHandler, TwitterRawHandler,
122 131
     private final String myPassword;
123 132
 
124 133
     /** Callback Manager for Twitter. */
125
-    private final CallbackManager<Twitter> myCallbackManager = new TwitterCallbackManager(this);
134
+    private final CallbackManager myCallbackManager = new CallbackManager(this, IMPL_MAP);
126 135
 
127 136
     /** String Convertor. */
128 137
     private final DefaultStringConverter myStringConverter = new DefaultStringConverter();
@@ -601,7 +610,7 @@ public class Twitter implements Parser, TwitterErrorHandler, TwitterRawHandler,
601 610
 
602 611
     /** {@inheritDoc} */
603 612
     @Override
604
-    public CallbackManager<? extends Parser> getCallbackManager() {
613
+    public CallbackManager getCallbackManager() {
605 614
         return myCallbackManager;
606 615
     }
607 616
 
@@ -1058,7 +1067,6 @@ public class Twitter implements Parser, TwitterErrorHandler, TwitterRawHandler,
1058 1067
         getCallbackManager().getCallbackType(ServerReadyListener.class).call();
1059 1068
         // Fake 005
1060 1069
         getCallbackManager().getCallbackType(NetworkDetectedListener.class).call(getNetworkName(), getServerSoftware(), getServerSoftwareType());
1061
-        getCallbackManager().getCallbackType(Post005Listener.class).call();
1062 1070
         // Fake MOTD
1063 1071
         getCallbackManager().getCallbackType(AuthNoticeListener.class).call("Welcome to " + myServerName + ".");
1064 1072
         getCallbackManager().getCallbackType(MotdStartListener.class).call("- " + myServerName + " Message of the Day -");

+ 0
- 71
src/com/dmdirc/addons/parser_twitter/TwitterCallbackObject.java Visa fil

@@ -1,71 +0,0 @@
1
-/*
2
- * Copyright (c) 2006-2009 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
-
23
-package com.dmdirc.addons.parser_twitter;
24
-
25
-import com.dmdirc.parser.common.CallbackManager;
26
-import com.dmdirc.parser.common.CallbackObject;
27
-import com.dmdirc.parser.interfaces.ChannelClientInfo;
28
-import com.dmdirc.parser.interfaces.ChannelInfo;
29
-import com.dmdirc.parser.interfaces.ClientInfo;
30
-import com.dmdirc.parser.interfaces.LocalClientInfo;
31
-import com.dmdirc.parser.interfaces.Parser;
32
-import com.dmdirc.parser.interfaces.callbacks.CallbackInterface;
33
-import java.util.HashMap;
34
-import java.util.Map;
35
-
36
-/**
37
- * A callback object for the Twitter parser.
38
- *
39
- * @since 0.6.3m2
40
- * @author chris
41
- */
42
-public class TwitterCallbackObject extends CallbackObject {
43
-
44
-    /** A map of interfaces to the classes which should be instansiated for them. */
45
-    protected static final Map<Class<?>, Class<?>> IMPL_MAP = new HashMap<Class<?>, Class<?>>();
46
-
47
-    static {
48
-        IMPL_MAP.put(ChannelClientInfo.class, TwitterChannelClientInfo.class);
49
-        IMPL_MAP.put(ChannelInfo.class, TwitterChannelInfo.class);
50
-        IMPL_MAP.put(ClientInfo.class, TwitterClientInfo.class);
51
-        IMPL_MAP.put(LocalClientInfo.class, TwitterClientInfo.class);
52
-    }
53
-
54
-    /**
55
-     * Create a new TwitterCallbackObject.
56
-     *
57
-     * @param parser Parser that owns this object.
58
-     * @param manager Callback Manager that owns this object.
59
-     * @param type Type of callback.
60
-     */
61
-    public TwitterCallbackObject(final Parser parser, final CallbackManager<?> manager, final Class<? extends CallbackInterface> type) {
62
-        super(parser, manager, type);
63
-    }
64
-
65
-    /** {@inheritDoc} */
66
-    @Override
67
-    protected Class<?> getImplementation(final Class<?> type) {
68
-        return IMPL_MAP.containsKey(type) ? IMPL_MAP.get(type) : type;
69
-    }
70
-
71
-}

+ 0
- 78
src/com/dmdirc/addons/parser_twitter/TwitterCallbackObjectSpecific.java Visa fil

@@ -1,78 +0,0 @@
1
-/*
2
- * Copyright (c) 2006-2009 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
-
23
-package com.dmdirc.addons.parser_twitter;
24
-
25
-import com.dmdirc.parser.common.CallbackManager;
26
-import com.dmdirc.parser.common.CallbackObjectSpecific;
27
-import com.dmdirc.parser.interfaces.ChannelClientInfo;
28
-import com.dmdirc.parser.interfaces.ChannelInfo;
29
-import com.dmdirc.parser.interfaces.ClientInfo;
30
-import com.dmdirc.parser.interfaces.LocalClientInfo;
31
-import com.dmdirc.parser.interfaces.Parser;
32
-import com.dmdirc.parser.interfaces.callbacks.CallbackInterface;
33
-import java.util.HashMap;
34
-import java.util.Map;
35
-
36
-/**
37
- * A specific callback object for use with the Twitter Parser
38
- *
39
- * @since 0.6.3m2
40
- * @author chris
41
- */
42
-public class TwitterCallbackObjectSpecific extends CallbackObjectSpecific {
43
-
44
-    /** A map of interfaces to the classes which should be instansiated for them. */
45
-    protected static final Map<Class<?>, Class<?>> IMPL_MAP = new HashMap<Class<?>, Class<?>>();
46
-
47
-    static {
48
-        IMPL_MAP.put(ChannelClientInfo.class, TwitterChannelClientInfo.class);
49
-        IMPL_MAP.put(ChannelInfo.class, TwitterChannelInfo.class);
50
-        IMPL_MAP.put(ClientInfo.class, TwitterClientInfo.class);
51
-        IMPL_MAP.put(LocalClientInfo.class, TwitterClientInfo.class);
52
-    }
53
-
54
-    /**
55
-     * Create a new TwitterCallbackObjectSpecific.
56
-     * This is a TwitterCallbackObject that has a specific target.
57
-     *
58
-     * @param parser Parser that owns this object.
59
-     * @param manager Callback Manager that owns this object.
60
-     * @param type Type of callback.
61
-     */
62
-    public TwitterCallbackObjectSpecific(final Parser parser, final CallbackManager<?> manager, final Class<? extends CallbackInterface> type) {
63
-        super(parser, manager, type);
64
-    }
65
-
66
-    /** {@inheritDoc} */
67
-    @Override
68
-    protected String translateHostname(final String hostname) {
69
-        return TwitterClientInfo.parseHost(hostname);
70
-    }
71
-
72
-    /** {@inheritDoc} */
73
-    @Override
74
-    protected Class<?> getImplementation(final Class<?> type) {
75
-        return IMPL_MAP.containsKey(type) ? IMPL_MAP.get(type) : type;
76
-    }
77
-
78
-}

+ 75
- 0
src/com/dmdirc/addons/parser_xmpp/XmppChannelClientInfo.java Visa fil

@@ -0,0 +1,75 @@
1
+/*
2
+ * Copyright (c) 2006-2011 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
+
23
+package com.dmdirc.addons.parser_xmpp;
24
+
25
+import com.dmdirc.parser.common.BaseChannelClientInfo;
26
+import com.dmdirc.parser.interfaces.ChannelClientInfo;
27
+import com.dmdirc.parser.interfaces.ChannelInfo;
28
+import com.dmdirc.parser.interfaces.ClientInfo;
29
+
30
+/**
31
+ * An XMPP-specific channel client info object.
32
+ */
33
+public class XmppChannelClientInfo extends BaseChannelClientInfo {
34
+
35
+    /**
36
+     * Creates a new client info object for the specified channel and client.
37
+     *
38
+     * @param channel The channel the association is with
39
+     * @param client The user that holds the association
40
+     */
41
+    public XmppChannelClientInfo(final ChannelInfo channel, final ClientInfo client) {
42
+        super(channel, client);
43
+    }
44
+
45
+    /** {@inheritDoc} */
46
+    @Override
47
+    public String getImportantModePrefix() {
48
+        return ""; // TODO: Implement
49
+    }
50
+
51
+    /** {@inheritDoc} */
52
+    @Override
53
+    public String getImportantMode() {
54
+        return ""; // TODO: Implement
55
+    }
56
+
57
+    /** {@inheritDoc} */
58
+    @Override
59
+    public String getAllModes() {
60
+        return ""; // TODO: Implement
61
+    }
62
+
63
+    /** {@inheritDoc} */
64
+    @Override
65
+    public void kick(final String message) {
66
+        throw new UnsupportedOperationException("Not supported yet.");
67
+    }
68
+
69
+    /** {@inheritDoc} */
70
+    @Override
71
+    public int compareTo(final ChannelClientInfo o) {
72
+        return 0; // TODO: Implement
73
+    }
74
+
75
+}

+ 171
- 0
src/com/dmdirc/addons/parser_xmpp/XmppClientInfo.java Visa fil

@@ -0,0 +1,171 @@
1
+/*
2
+ * Copyright (c) 2006-2011 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
+
23
+package com.dmdirc.addons.parser_xmpp;
24
+
25
+import com.dmdirc.parser.common.BaseClientInfo;
26
+import com.dmdirc.parser.interfaces.Parser;
27
+import java.util.ArrayList;
28
+
29
+import java.util.Collections;
30
+import java.util.HashMap;
31
+import java.util.HashSet;
32
+import java.util.List;
33
+import java.util.Map;
34
+import java.util.Set;
35
+
36
+import org.jivesoftware.smack.RosterEntry;
37
+import org.jivesoftware.smack.packet.Presence;
38
+import org.jivesoftware.smack.packet.Presence.Mode;
39
+import org.jivesoftware.smack.packet.Presence.Type;
40
+
41
+/**
42
+ * An XMPP-specific client info object.
43
+ */
44
+public class XmppClientInfo extends BaseClientInfo {
45
+
46
+    /** A map of known endpoints for this client. */
47
+    private final Map<String, XmppEndpoint> endpoints
48
+            = new HashMap<String, XmppEndpoint>();
49
+
50
+    /**
51
+     * Creates a new XMPP Client Info object with the specified details.
52
+     *
53
+     * @param parser The parser that owns this client info object
54
+     * @param nick The nickname of the user this object represents
55
+     * @param user The username of the user this object represents
56
+     * @param host The hostname of the user this object represents
57
+     */
58
+    public XmppClientInfo(final Parser parser, final String nick,
59
+            final String user, final String host) {
60
+        super(parser, nick, user, host);
61
+    }
62
+
63
+    /** {@inheritDoc} */
64
+    @Override
65
+    public int getChannelCount() {
66
+        return 0;
67
+    }
68
+
69
+    /**
70
+     * Retrieves the map of known endpoints for this client.
71
+     *
72
+     * @return A map of endpoint names to presence states.
73
+     */
74
+    public Map<String, XmppEndpoint> getEndpoints() {
75
+        return Collections.unmodifiableMap(endpoints);
76
+    }
77
+
78
+    /**
79
+     * Sets the presence of one endpoint of this client.
80
+     *
81
+     * @param packet The presence packet received
82
+     */
83
+    public void setPresence(final Presence packet) {
84
+        final String[] parts = getParser().parseHostmask(packet.getFrom());
85
+        final boolean wasAway = isAway();
86
+
87
+        if (packet.getType() == Type.unavailable) {
88
+            endpoints.remove(parts[1]);
89
+            return;
90
+        }
91
+
92
+        if (!endpoints.containsKey(parts[1])) {
93
+            endpoints.put(parts[1], new XmppEndpoint());
94
+        }
95
+
96
+        final XmppEndpoint endpoint = endpoints.get(parts[1]);
97
+        endpoint.setPriority(packet.getPriority());
98
+        endpoint.setMode(packet.getMode());
99
+        endpoint.setType(packet.getType());
100
+        endpoint.setStatus(packet.getStatus());
101
+
102
+        final boolean isAway = isAway();
103
+
104
+        if (wasAway != isAway) {
105
+            // Their away status has changed
106
+            ((XmppParser) getParser()).handleAwayStateChange(this, wasAway);
107
+        }
108
+    }
109
+
110
+    /**
111
+     * Determines whether or not this contact is "away" - that is, their
112
+     * aggregate presence state is "Unavailable", "Away", "DND" or "XA".
113
+     *
114
+     * @return True if the client is away, false otherwise
115
+     */
116
+    public boolean isAway() {
117
+        final Mode presence = getAggregatePresence();
118
+
119
+        return presence == null || presence.compareTo(Mode.away) >= 0;
120
+    }
121
+
122
+    /**
123
+     * Gets the aggregate presence of this contact.
124
+     *
125
+     * @return The highest available state of the contact's highest priority
126
+     * endpoints, or <code>null</code> if no endpoints are available.
127
+     */
128
+    public Mode getAggregatePresence() {
129
+        final List<XmppEndpoint> sortedEndpoints
130
+                = new ArrayList<XmppEndpoint>(endpoints.values());
131
+        Collections.sort(sortedEndpoints);
132
+
133
+        // Get the set of the highest priority endpoints
134
+        final Set<XmppEndpoint> bestEndpoints = new HashSet<XmppEndpoint>(sortedEndpoints.size());
135
+        int best = 0;
136
+        for (XmppEndpoint endpoint : sortedEndpoints) {
137
+            if (endpoint.getType() != Type.available) {
138
+                continue;
139
+            }
140
+
141
+            if (bestEndpoints.isEmpty()) {
142
+                best = endpoint.getPriority();
143
+            }
144
+
145
+            if (endpoint.getPriority() == best && best >= 0) {
146
+                bestEndpoints.add(endpoint);
147
+            }
148
+        }
149
+
150
+        // Find the best presence
151
+        Mode bestPresence = null;
152
+        for (XmppEndpoint endpoint : bestEndpoints) {
153
+            final Mode targetMode = endpoint.getMode() == null ? Mode.available : endpoint.getMode();
154
+            if (bestPresence == null || bestPresence.compareTo(targetMode) > 0) {
155
+                bestPresence = targetMode;
156
+            }
157
+        }
158
+
159
+        return bestPresence;
160
+    }
161
+
162
+    /**
163
+     * Updates this client with information from the roster.
164
+     *
165
+     * @param entry The roster entry to update this client with
166
+     */
167
+    public void setRosterEntry(final RosterEntry entry) {
168
+        setRealname(entry.getName());
169
+    }
170
+
171
+}

+ 160
- 0
src/com/dmdirc/addons/parser_xmpp/XmppEndpoint.java Visa fil

@@ -0,0 +1,160 @@
1
+/*
2
+ * Copyright (c) 2006-2011 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
+
23
+package com.dmdirc.addons.parser_xmpp;
24
+
25
+import org.jivesoftware.smack.packet.Presence;
26
+import org.jivesoftware.smack.packet.Presence.Mode;
27
+import org.jivesoftware.smack.packet.Presence.Type;
28
+
29
+/**
30
+ * Represents a single endpoint of one contact.
31
+ */
32
+public class XmppEndpoint implements Comparable<XmppEndpoint> {
33
+
34
+    /** The mode of the last presence update. */
35
+    private Presence.Mode mode;
36
+    /** The type of the last presence update. */
37
+    private Presence.Type type;
38
+    /** The status from the last presence update. */
39
+    private String status;
40
+    /** The priority of this endpoint. */
41
+    private int priority;
42
+
43
+    /**
44
+     * Gets the mode of this endpoint.
45
+     *
46
+     * @return The last seen presence mode
47
+     */
48
+    public Mode getMode() {
49
+        return mode;
50
+    }
51
+
52
+    /**
53
+     * Sets the mode of this endpoint.
54
+     *
55
+     * @param mode The most recently received presence mode
56
+     */
57
+    public void setMode(final Mode mode) {
58
+        this.mode = mode;
59
+    }
60
+
61
+    /**
62
+     * Gets the status of this endpoint.
63
+     *
64
+     * @return The last seen presence status
65
+     */
66
+    public String getStatus() {
67
+        return status;
68
+    }
69
+
70
+    /**
71
+     * Sets the status of this endpoint.
72
+     *
73
+     * @param status The most recently received presence status
74
+     */
75
+    public void setStatus(final String status) {
76
+        this.status = status;
77
+    }
78
+
79
+    /**
80
+     * Gets the type of this endpoint.
81
+     *
82
+     * @return The last seen presence type
83
+     */
84
+    public Type getType() {
85
+        return type;
86
+    }
87
+
88
+    /**
89
+     * Sets the type of this endpoint.
90
+     *
91
+     * @param type The most recently received presence type
92
+     */
93
+    public void setType(final Type type) {
94
+        this.type = type;
95
+    }
96
+
97
+    /**
98
+     * Gets the priority of this endpoint.
99
+     *
100
+     * @return The priority of this endpoint
101
+     */
102
+    public int getPriority() {
103
+        return priority;
104
+    }
105
+
106
+    /**
107
+     * Sets the priority of this endpoint.
108
+     *
109
+     * @param priority The new priority for this endpoint
110
+     */
111
+    public void setPriority(final int priority) {
112
+        this.priority = priority;
113
+    }
114
+
115
+    /**
116
+     * Gets a user-friendly string describing this endpoint's presence.
117
+     *
118
+     * @return The presence of this endpoint in a user-friendly format
119
+     */
120
+    public String getPresence() {
121
+        final String statusText = (status == null || status.isEmpty()) ? "" : " (" + status + ")";
122
+
123
+        if (type == Type.unavailable) {
124
+            return "Unavailable" + statusText;
125
+        } else if (mode == null) {
126
+            return "Available" + statusText;
127
+        } else {
128
+            return getFriendlyMode(mode) + statusText;
129
+        }
130
+    }
131
+
132
+    /**
133
+     * Returns a friendly representation of the specified presence mode.
134
+     *
135
+     * @param mode The mode being represented
136
+     * @return A user-friendly string corresponding to the mode
137
+     */
138
+    private static String getFriendlyMode(final Mode mode) {
139
+        switch (mode) {
140
+            case available:
141
+                return "Available";
142
+            case away:
143
+                return "Away";
144
+            case chat:
145
+                return "Chatty";
146
+            case dnd:
147
+                return "Do not disturb";
148
+            case xa:
149
+                return "Extended Away";
150
+            default:
151
+                return mode.toString();
152
+        }
153
+    }
154
+
155
+    /** {@inheritDoc} */
156
+    @Override
157
+    public int compareTo(final XmppEndpoint o) {
158
+        return o.getPriority() - priority;
159
+    }
160
+}

+ 155
- 0
src/com/dmdirc/addons/parser_xmpp/XmppFakeChannel.java Visa fil

@@ -0,0 +1,155 @@
1
+/*
2
+ * Copyright (c) 2006-2011 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
+
23
+package com.dmdirc.addons.parser_xmpp;
24
+
25
+import com.dmdirc.parser.common.BaseChannelInfo;
26
+import com.dmdirc.parser.common.ChannelListModeItem;
27
+import com.dmdirc.parser.interfaces.ChannelClientInfo;
28
+import com.dmdirc.parser.interfaces.ClientInfo;
29
+import com.dmdirc.parser.interfaces.Parser;
30
+import com.dmdirc.parser.interfaces.callbacks.ChannelNamesListener;
31
+
32
+import java.util.Collection;
33
+
34
+/**
35
+ * A 'fake' local channel used to display buddy lists.
36
+ */
37
+public class XmppFakeChannel extends BaseChannelInfo {
38
+
39
+    /**
40
+     * Creates a new fake channel belonging to the specified parser and
41
+     * with the given name.
42
+     *
43
+     * @param parser The XMPP parser that owns this channel
44
+     * @param name The name of the channel
45
+     */
46
+    public XmppFakeChannel(final Parser parser, final String name) {
47
+        super(parser, name);
48
+    }
49
+
50
+    /** {@inheritDoc} */
51
+    @Override
52
+    public void setTopic(final String topic) {
53
+        throw new UnsupportedOperationException("Not supported yet.");
54
+    }
55
+
56
+    /** {@inheritDoc} */
57
+    @Override
58
+    public String getTopic() {
59
+        return "No topic yet!";
60
+    }
61
+
62
+    /** {@inheritDoc} */
63
+    @Override
64
+    public long getTopicTime() {
65
+        return System.currentTimeMillis(); // TODO
66
+    }
67
+
68
+    /** {@inheritDoc} */
69
+    @Override
70
+    public String getTopicSetter() {
71
+        return ""; // TODO
72
+    }
73
+
74
+    /** {@inheritDoc} */
75
+    @Override
76
+    public String getModes() {
77
+        return ""; // TODO
78
+    }
79
+
80
+    /** {@inheritDoc} */
81
+    @Override
82
+    public String getMode(final char mode) {
83
+        throw new UnsupportedOperationException("Not supported yet.");
84
+    }
85
+
86
+    /** {@inheritDoc} */
87
+    @Override
88
+    public Collection<ChannelListModeItem> getListMode(final char mode) {
89
+        throw new UnsupportedOperationException("Not supported yet.");
90
+    }
91
+
92
+    /** {@inheritDoc} */
93
+    @Override
94
+    public void part(final String reason) {
95
+        throw new UnsupportedOperationException("Not supported yet.");
96
+    }
97
+
98
+    /** {@inheritDoc} */
99
+    @Override
100
+    public void sendWho() {
101
+        throw new UnsupportedOperationException("Not supported yet.");
102
+    }
103
+
104
+    /** {@inheritDoc} */
105
+    @Override
106
+    public void alterMode(final boolean add, final Character mode, final String parameter) {
107
+        throw new UnsupportedOperationException("Not supported yet.");
108
+    }
109
+
110
+    /** {@inheritDoc} */
111
+    @Override
112
+    public void flushModes() {
113
+        throw new UnsupportedOperationException("Not supported yet.");
114
+    }
115
+
116
+    /** {@inheritDoc} */
117
+    @Override
118
+    public void requestListModes() {
119
+        throw new UnsupportedOperationException("Not supported yet.");
120
+    }
121
+
122
+    /** {@inheritDoc} */
123
+    @Override
124
+    public ChannelClientInfo getChannelClient(final ClientInfo client) {
125
+        return getClient(client.getNickname());
126
+    }
127
+
128
+    /** {@inheritDoc} */
129
+    @Override
130
+    public ChannelClientInfo getChannelClient(final String client, final boolean create) {
131
+        final String[] parts = getParser().parseHostmask(client);
132
+        
133
+        if (create && getClient(parts[0]) == null) {
134
+            return new XmppChannelClientInfo(this, getParser().getClient(client));
135
+        }
136
+        
137
+        return getClient(parts[0]);
138
+    }
139
+    
140
+    /**
141
+     * Updates this channel with the specified contacts.
142
+     *
143
+     * @param clients The contacts to be added
144
+     */
145
+    public void updateContacts(final Collection<XmppClientInfo> clients) {
146
+        for (XmppClientInfo client : clients) {
147
+            addClient(client.getNickname(), new XmppChannelClientInfo(this, client));
148
+        }
149
+        
150
+        // TODO: Delete old contacts, don't needlessly create new objects
151
+        
152
+        getParser().getCallbackManager().getCallbackType(ChannelNamesListener.class).call(this);
153
+    }
154
+
155
+}

+ 82
- 0
src/com/dmdirc/addons/parser_xmpp/XmppLocalClientInfo.java Visa fil

@@ -0,0 +1,82 @@
1
+/*
2
+ * Copyright (c) 2006-2011 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
+
23
+package com.dmdirc.addons.parser_xmpp;
24
+
25
+import com.dmdirc.parser.interfaces.LocalClientInfo;
26
+import com.dmdirc.parser.interfaces.Parser;
27
+
28
+/**
29
+ * A representation of the local XMPP client.
30
+ */
31
+public class XmppLocalClientInfo extends XmppClientInfo implements LocalClientInfo {
32
+
33
+    /**
34
+     * Creates a new XMPP Client Info object with the specified details.
35
+     *
36
+     * @param parser The parser that owns this client info object
37
+     * @param nick The nickname of the user this object represents
38
+     * @param user The username of the user this object represents
39
+     * @param host The hostname of the user this object represents
40
+     */
41
+    public XmppLocalClientInfo(final Parser parser, final String nick,
42
+            final String user, final String host) {
43
+        super(parser, nick, user, host);
44
+    }
45
+
46
+    /** {@inheritDoc} */
47
+    @Override
48
+    public void setNickname(final String name) {
49
+        throw new UnsupportedOperationException("Not supported yet.");
50
+    }
51
+
52
+    /** {@inheritDoc} */
53
+    @Override
54
+    public String getModes() {
55
+        throw new UnsupportedOperationException("Not supported yet.");
56
+    }
57
+
58
+    /** {@inheritDoc} */
59
+    @Override
60
+    public void setAway(final String reason) {
61
+        throw new UnsupportedOperationException("Not supported yet.");
62
+    }
63
+
64
+    /** {@inheritDoc} */
65
+    @Override
66
+    public void setBack() {
67
+        throw new UnsupportedOperationException("Not supported yet.");
68
+    }
69
+
70
+    /** {@inheritDoc} */
71
+    @Override
72
+    public void alterMode(final boolean add, final Character mode) {
73
+        throw new UnsupportedOperationException("Not supported yet.");
74
+    }
75
+
76
+    /** {@inheritDoc} */
77
+    @Override
78
+    public void flushModes() {
79
+        throw new UnsupportedOperationException("Not supported yet.");
80
+    }
81
+
82
+}

+ 604
- 0
src/com/dmdirc/addons/parser_xmpp/XmppParser.java Visa fil

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

+ 68
- 0
src/com/dmdirc/addons/parser_xmpp/XmppPlugin.java Visa fil

@@ -0,0 +1,68 @@
1
+/*
2
+ * Copyright (c) 2006-2011 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
+
23
+package com.dmdirc.addons.parser_xmpp;
24
+
25
+import com.dmdirc.parser.common.MyInfo;
26
+import com.dmdirc.parser.interfaces.Parser;
27
+import com.dmdirc.parser.interfaces.ProtocolDescription;
28
+import com.dmdirc.plugins.Plugin;
29
+import java.net.URI;
30
+
31
+/**
32
+ * Plugin that provides a parser to connect to XMPP services.
33
+ */
34
+public class XmppPlugin extends Plugin {
35
+
36
+    /** {@inheritDoc} */
37
+    @Override
38
+    public void onLoad() {
39
+        // Do nothing
40
+    }
41
+
42
+    /** {@inheritDoc} */
43
+    @Override
44
+    public void onUnload() {
45
+        // Do nothing
46
+    }
47
+    
48
+    /**
49
+     * Creates a new XMPP parser for the specified client info and address.
50
+     *
51
+     * @param myInfo The settings for the local client
52
+     * @param address The address to connect to
53
+     * @return An appropriately configured parser
54
+     */
55
+    public Parser getParser(final MyInfo myInfo, final URI address) {
56
+        return new XmppParser(address);
57
+    }
58
+    
59
+    /**
60
+     * Retrieves an object describing the properties of the XMPP protocol.
61
+     *
62
+     * @return A relevant protocol description object
63
+     */
64
+    public ProtocolDescription getDescription() {
65
+        return new XmppProtocolDescription();
66
+    }
67
+
68
+}

src/com/dmdirc/addons/parser_twitter/TwitterCallbackManager.java → src/com/dmdirc/addons/parser_xmpp/XmppProtocolDescription.java Visa fil

@@ -1,5 +1,5 @@
1 1
 /*
2
- * Copyright (c) 2006-2009 Chris Smith, Shane Mc Cormack, Gregory Holmes
2
+ * Copyright (c) 2006-2011 Chris Smith, Shane Mc Cormack, Gregory Holmes
3 3
  *
4 4
  * Permission is hereby granted, free of charge, to any person obtaining a copy
5 5
  * of this software and associated documentation files (the "Software"), to deal
@@ -20,39 +20,26 @@
20 20
  * SOFTWARE.
21 21
  */
22 22
 
23
-package com.dmdirc.addons.parser_twitter;
23
+package com.dmdirc.addons.parser_xmpp;
24 24
 
25
-import com.dmdirc.parser.common.CallbackManager;
26
-import com.dmdirc.parser.common.CallbackObject;
27
-import com.dmdirc.parser.common.CallbackObjectSpecific;
28
-import com.dmdirc.parser.interfaces.callbacks.CallbackInterface;
25
+import com.dmdirc.parser.interfaces.ProtocolDescription;
29 26
 
30 27
 /**
31
- * Handles callbacks for the Twitter Parser.
32
- * 
33
- * @author chris
28
+ * A description of the XMPP protocol.
34 29
  */
35
-public class TwitterCallbackManager extends CallbackManager<Twitter> {
36
-
37
-    /**
38
-     * Create a new TwitterCallbackManager
39
-     * 
40
-     * @param parser Parser that owns this callback manager.
41
-     */
42
-    public TwitterCallbackManager(final Twitter parser) {
43
-        super(parser);
44
-    }
30
+public class XmppProtocolDescription implements ProtocolDescription {
45 31
 
46 32
     /** {@inheritDoc} */
47 33
     @Override
48
-    protected CallbackObject getCallbackObject(final Twitter parser, final Class<?> type) {
49
-        return new TwitterCallbackObject(parser, this, type.asSubclass(CallbackInterface.class));
34
+    public int getDefaultPort() {
35
+        return 5222;
50 36
     }
51 37
 
52 38
     /** {@inheritDoc} */
53 39
     @Override
54
-    protected CallbackObjectSpecific getSpecificCallbackObject(final Twitter parser, final Class<?> type) {
55
-        return new TwitterCallbackObjectSpecific(parser, this, type.asSubclass(CallbackInterface.class));
40
+    public String[] parseHostmask(final String hostmask) {
41
+        final String[] parts = hostmask.split("/");
42
+        return new String[] { parts[0], parts.length > 1 ? parts[1] : "unknown", "" };
56 43
     }
57 44
 
58 45
 }

+ 34
- 0
src/com/dmdirc/addons/parser_xmpp/plugin.config Visa fil

@@ -0,0 +1,34 @@
1
+# This is a DMDirc configuration file.
2
+
3
+# This section indicates which sections below take key/value
4
+# pairs, rather than a simple list. It should be placed above
5
+# any sections that take key/values.
6
+keysections:
7
+  metadata
8
+  updates
9
+  version
10
+  defaults
11
+  icons
12
+
13
+metadata:
14
+  author=Chris <chris@dmdirc.com>
15
+  mainclass=com.dmdirc.addons.parser_xmpp.XmppPlugin
16
+  description=Provides XMPP access for DMDirc.
17
+  name=xmpp
18
+  nicename=XMPP Plugin
19
+
20
+updates:
21
+  id=63
22
+
23
+version:
24
+  number=1
25
+  friendly=0.1
26
+
27
+provides:
28
+  xmpp parser
29
+  xmpps parser
30
+
31
+
32
+exports:
33
+  getParser in com.dmdirc.addons.parser_xmpp.XmppPlugin as getParser
34
+  getDescription in com.dmdirc.addons.parser_xmpp.XmppPlugin as getProtocolDescription

+ 7
- 8
src/com/dmdirc/addons/relaybot/RelayCallbackManager.java Visa fil

@@ -31,7 +31,6 @@ import com.dmdirc.parser.interfaces.Parser;
31 31
 import com.dmdirc.parser.interfaces.callbacks.CallbackInterface;
32 32
 import com.dmdirc.parser.interfaces.callbacks.ChannelMessageListener;
33 33
 import com.dmdirc.parser.interfaces.callbacks.SocketCloseListener;
34
-import com.dmdirc.parser.irc.IRCCallbackManager;
35 34
 import com.dmdirc.parser.irc.IRCParser;
36 35
 
37 36
 import java.lang.reflect.Field;
@@ -49,13 +48,13 @@ import java.util.Map;
49 48
  *
50 49
  * @author shane
51 50
  */
52
-public class RelayCallbackManager extends IRCCallbackManager implements SocketCloseListener {
51
+public class RelayCallbackManager extends CallbackManager implements SocketCloseListener {
53 52
 
54 53
     /** Pluign that created this callback manager. */
55 54
     private final RelayBotPlugin myPlugin;
56 55
 
57 56
     /** Original CallbackManager */
58
-    private final IRCCallbackManager originalCBM;
57
+    private final CallbackManager originalCBM;
59 58
 
60 59
     /**
61 60
      * Create a new RelayCallbackManager and replace
@@ -64,17 +63,17 @@ public class RelayCallbackManager extends IRCCallbackManager implements SocketCl
64 63
      * @param parser
65 64
      */
66 65
     public RelayCallbackManager(final RelayBotPlugin myPlugin, final IRCParser parser) {
67
-        super(parser);
66
+        super(parser, IRCParser.IMPL_MAP);
68 67
 
69 68
         this.myPlugin = myPlugin;
70
-        this.originalCBM = (IRCCallbackManager) parser.getCallbackManager();
69
+        this.originalCBM = parser.getCallbackManager();
71 70
         setCallbackManager(parser, this);
72 71
         addCallback(SocketCloseListener.class, this);
73 72
     }
74 73
 
75 74
     /** {@inheritDoc} */
76 75
     @Override
77
-    public <T extends CallbackInterface> void addCallback(final Class<T> callback, final T o, final String target) throws CallbackNotFoundException {
76
+    public <S extends CallbackInterface> void addCallback(final Class<S> callback, final S o, final String target) throws CallbackNotFoundException {
78 77
         // Don't allow the core to give itself a ChannelMessageListener if we
79 78
         // already have a listener for this channel.
80 79
         if (o instanceof ChannelEventHandler && callback == ChannelMessageListener.class) {
@@ -125,13 +124,13 @@ public class RelayCallbackManager extends IRCCallbackManager implements SocketCl
125 124
      * @param parser
126 125
      * @param cbm
127 126
      */
128
-    private void setCallbackManager(final IRCParser parser, final CallbackManager<IRCParser> cbm) {
127
+    private void setCallbackManager(final IRCParser parser, final CallbackManager cbm) {
129 128
         try {
130 129
             // Get the old callback manager
131 130
             final Field field = parser.getClass().getDeclaredField("myCallbackManager");
132 131
             field.setAccessible(true);
133 132
             @SuppressWarnings("unchecked")
134
-            final CallbackManager<IRCParser> oldCBM = (CallbackManager<IRCParser>) field.get(parser);
133
+            final CallbackManager oldCBM = (CallbackManager) field.get(parser);
135 134
 
136 135
             // Clone the known CallbackObjects list (horrible code ahoy!)
137 136
             // First get the old map of callbacks

+ 5
- 5
src/com/dmdirc/addons/relaybot/RelayChannelHandler.java Visa fil

@@ -25,11 +25,11 @@ package com.dmdirc.addons.relaybot;
25 25
 import com.dmdirc.Channel;
26 26
 import com.dmdirc.ChannelEventHandler;
27 27
 import com.dmdirc.config.IdentityManager;
28
+import com.dmdirc.parser.common.CallbackManager;
28 29
 import com.dmdirc.parser.interfaces.ChannelClientInfo;
29 30
 import com.dmdirc.parser.interfaces.ChannelInfo;
30 31
 import com.dmdirc.parser.interfaces.Parser;
31 32
 import com.dmdirc.parser.interfaces.callbacks.ChannelMessageListener;
32
-import com.dmdirc.parser.irc.IRCCallbackManager;
33 33
 import com.dmdirc.parser.irc.IRCChannelClientInfo;
34 34
 import com.dmdirc.parser.irc.IRCParser;
35 35
 import com.dmdirc.plugins.PluginInfo;
@@ -94,8 +94,8 @@ public class RelayChannelHandler implements ChannelMessageListener {
94 94
         coreChannelHandler = ceh;
95 95
 
96 96
         if (coreChannelHandler != null) {
97
-            final IRCCallbackManager cbm = (IRCCallbackManager) myChannel
98
-                    .getServer().getParser().getCallbackManager();
97
+            final CallbackManager cbm = myChannel.getServer().getParser()
98
+                    .getCallbackManager();
99 99
             cbm.delCallback(ChannelMessageListener.class, coreChannelHandler);
100 100
             cbm.addCallback(ChannelMessageListener.class, this,
101 101
                     myChannel.getName());
@@ -181,8 +181,8 @@ public class RelayChannelHandler implements ChannelMessageListener {
181 181
      */
182 182
     public void restoreCoreChannelHandler() {
183 183
         if (coreChannelHandler != null) {
184
-            final IRCCallbackManager cbm = (IRCCallbackManager) myChannel
185
-                    .getServer().getParser().getCallbackManager();
184
+            final CallbackManager cbm = myChannel.getServer().getParser()
185
+                    .getCallbackManager();
186 186
 
187 187
             // Force adding this callback to the CBM.
188 188
             if (cbm instanceof RelayCallbackManager) {

Laddar…
Avbryt
Spara