File Source: Server.java
/*
P/P * Method: com.dmdirc.Server$1__static_init
*/
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;
24
25 import com.dmdirc.actions.ActionManager;
26 import com.dmdirc.actions.CoreActionType;
27 import com.dmdirc.actions.wrappers.AliasWrapper;
28 import com.dmdirc.commandparser.CommandManager;
29 import com.dmdirc.commandparser.CommandType;
30 import com.dmdirc.config.ConfigManager;
31 import com.dmdirc.config.Identity;
32 import com.dmdirc.config.IdentityManager;
33 import com.dmdirc.interfaces.AwayStateListener;
34 import com.dmdirc.interfaces.InviteListener;
35 import com.dmdirc.logger.ErrorLevel;
36 import com.dmdirc.logger.Logger;
37 import com.dmdirc.parser.irc.ChannelInfo;
38 import com.dmdirc.parser.irc.ClientInfo;
39 import com.dmdirc.parser.irc.IRCParser;
40 import com.dmdirc.parser.irc.IRCStringConverter;
41 import com.dmdirc.parser.irc.MyInfo;
42 import com.dmdirc.parser.irc.ParserError;
43 import com.dmdirc.parser.irc.ServerInfo;
44 import com.dmdirc.ui.WindowManager;
45 import com.dmdirc.ui.input.TabCompleter;
46 import com.dmdirc.ui.input.TabCompletionType;
47 import com.dmdirc.ui.interfaces.InputWindow;
48 import com.dmdirc.ui.interfaces.ServerWindow;
49 import com.dmdirc.ui.interfaces.Window;
50
51 import java.io.Serializable;
52 import java.util.ArrayList;
53 import java.util.Hashtable;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Timer;
57 import java.util.TimerTask;
58
59 import javax.net.ssl.TrustManager;
60
61 /**
62 * The Server class represents the client's view of a server. It maintains
63 * a list of all channels, queries, etc, and handles parser callbacks pertaining
64 * to the server.
65 *
66 * @author chris
67 */
/*
P/P * Method: com.dmdirc.Server__static_init
*
* Postconditions:
* DOMAIN_GENERAL != null
* DOMAIN_PROFILE != null
* DOMAIN_SERVER != null
*/
68 public class Server extends WritableFrameContainer implements Serializable {
69
70 // <editor-fold defaultstate="collapsed" desc="Properties">
71
72 // <editor-fold defaultstate="collapsed" desc="Static">
73
74 /**
75 * A version number for this class. It should be changed whenever the class
76 * structure is changed (or anything else that would prevent serialized
77 * objects being unserialized with the new class).
78 */
79 private static final long serialVersionUID = 1;
80
81 /** The name of the general domain. */
82 private static final String DOMAIN_GENERAL = "general".intern();
83 /** The name of the profile domain. */
84 private static final String DOMAIN_PROFILE = "profile".intern();
85 /** The name of the server domain. */
86 private static final String DOMAIN_SERVER = "server".intern();
87
88 // </editor-fold>
89
90 // <editor-fold defaultstate="collapsed" desc="Instance">
91
92 /** Open channels that currently exist on the server. */
93 private final Map<String, Channel> channels = new Hashtable<String, Channel>();
94 /** Open query windows on the server. */
95 private final List<Query> queries = new ArrayList<Query>();
96
97 /** The IRC Parser instance handling this server. */
98 private transient IRCParser parser;
99 /** The IRC Parser Thread. */
100 private transient Thread parserThread;
101 /** The raw frame used for this server instance. */
102 private Raw raw;
103 /** The ServerWindow corresponding to this server. */
104 private ServerWindow window;
105
106 /** The details of the server we're connecting to. */
107 private ServerInfo serverInfo;
108
109 /** The profile we're using. */
110 private transient Identity profile;
111
112 /** The current state of this server. */
113 private final ServerStatus myState = new ServerStatus();
114
115 /** The timer we're using to delay reconnects. */
116 private Timer reconnectTimer;
117
118 /** Channels we're meant to auto-join. */
119 private final List<String> autochannels;
120
121 /** The tabcompleter used for this server. */
122 private final TabCompleter tabCompleter = new TabCompleter();
123 /** The last activated internal frame for this server. */
124 private FrameContainer activeFrame = this;
125
126 /** Our reason for being away, if any. */
127 private String awayMessage;
128
129 /** Our event handler. */
130 private final ServerEventHandler eventHandler = new ServerEventHandler(this);
131
132 /** A list of outstanding invites. */
133 private final List<Invite> invites = new ArrayList<Invite>();
134
135 /** Our ignore list. */
136 private final IgnoreList ignoreList = new IgnoreList();
137
138 /** Our string convertor. */
139 private IRCStringConverter converter = new IRCStringConverter();
140
141 /** The parser factory to use. */
142 private final ParserFactory parserFactory;
143
144 // </editor-fold>
145
146 // </editor-fold>
147
148 // <editor-fold defaultstate="collapsed" desc="Constructors">
149
150 /**
151 * Creates a new instance of Server. Does not auto-join any channels, and
152 * uses a default {@link ParserFactory}.
153 *
154 * @param server The hostname/ip of the server to connect to
155 * @param port The port to connect to
156 * @param password The server password
157 * @param ssl Whether to use SSL or not
158 * @param profile The profile to use
159 */
160 public Server(final String server, final int port, final String password,
161 final boolean ssl, final Identity profile) {
/*
P/P * Method: void com.dmdirc.Server(String, int, String, bool, Identity)
*
* Preconditions:
* (soft) init'ed(com/dmdirc/ServerManager.me)
* (soft) init'ed(com/dmdirc/actions/wrappers/AliasWrapper.me)
*
* Postconditions:
* com/dmdirc/ServerManager.me == old com/dmdirc/ServerManager.me
* com/dmdirc/actions/wrappers/AliasWrapper.me == old com/dmdirc/actions/wrappers/AliasWrapper.me
* init'ed(this.activeFrame)
* this.autochannels == undefined
* this.autochannels == null
* this.changer == this.autochannels
* this.channels == this.autochannels
* this.config == this.autochannels
* this.eventHandler == this.autochannels
* this.ignoreList == this.autochannels
* ...
*/
162 this(server, port, password, ssl, profile, new ArrayList<String>());
163 }
164
165 /**
166 * Creates a new instance of Server. Uses a default {@link ParserFactory}.
167 *
168 * @param server The hostname/ip of the server to connect to
169 * @param port The port to connect to
170 * @param password The server password
171 * @param ssl Whether to use SSL or not
172 * @param profile The profile to use
173 * @param autochannels A list of channels to auto-join when we connect
174 */
175 public Server(final String server, final int port, final String password,
176 final boolean ssl, final Identity profile, final List<String> autochannels) {
/*
P/P * Method: void com.dmdirc.Server(String, int, String, bool, Identity, List)
*
* Preconditions:
* (soft) init'ed(com/dmdirc/ServerManager.me)
* (soft) init'ed(com/dmdirc/actions/wrappers/AliasWrapper.me)
*
* Postconditions:
* com/dmdirc/ServerManager.me == old com/dmdirc/ServerManager.me
* com/dmdirc/actions/wrappers/AliasWrapper.me == old com/dmdirc/actions/wrappers/AliasWrapper.me
* init'ed(this.activeFrame)
* this.autochannels == undefined
* this.autochannels == null
* this.changer == this.autochannels
* this.channels == this.autochannels
* this.config == this.autochannels
* this.eventHandler == this.autochannels
* this.ignoreList == this.autochannels
* ...
*/
177 this(server, port, password, ssl, profile, autochannels, new ParserFactory());
178 }
179
180 /**
181 * Creates a new instance of Server.
182 *
183 * @since 0.6
184 * @param server The hostname/ip of the server to connect to
185 * @param port The port to connect to
186 * @param password The server password
187 * @param ssl Whether to use SSL or not
188 * @param profile The profile to use
189 * @param autochannels A list of channels to auto-join when we connect
190 * @param factory The {@link ParserFactory} to use to create parsers
191 */
192 public Server(final String server, final int port, final String password,
193 final boolean ssl, final Identity profile,
194 final List<String> autochannels, final ParserFactory factory) {
/*
P/P * Method: void com.dmdirc.Server(String, int, String, bool, Identity, List, ParserFactory)
*
* Preconditions:
* com/dmdirc/Main.controller != null
* init'ed(com/dmdirc/ServerManager.me)
* init'ed(com/dmdirc/actions/wrappers/AliasWrapper.me)
*
* Presumptions:
* init'ed(com.dmdirc.commandparser.CommandType.TYPE_GLOBAL)
* init'ed(com.dmdirc.commandparser.CommandType.TYPE_SERVER)
* init'ed(com.dmdirc.ui.input.TabCompletionType.COMMAND)
* com.dmdirc.ui.interfaces.ServerWindow:getInputHandler(...)@209 != null
* com.dmdirc.ui.interfaces.UIController:getServer(...)@200 != null
* ...
*
* Postconditions:
* com/dmdirc/ServerManager.me == One-of{old com/dmdirc/ServerManager.me, &new ServerManager(getServerManager#1)}
* com/dmdirc/ServerManager.me != null
* com/dmdirc/actions/wrappers/AliasWrapper.me == One-of{old com/dmdirc/actions/wrappers/AliasWrapper.me, &new AliasWrapper(getAliasWrapper#1)}
* com/dmdirc/actions/wrappers/AliasWrapper.me != null
* this.activeFrame == this
* this.activeFrame != null
* this.eventHandler.owner == this.activeFrame
* this.autochannels == autochannels
* init'ed(this.autochannels)
* this.changer == &new FrameContainer$IconChanger(FrameContainer#2)
* ...
*
* Test Vectors:
* com.dmdirc.config.ConfigManager:getOptionBool(...)@232: {0}, {1}
*/
195 super("server-disconnected", new ConfigManager("", "", server));
196
197 serverInfo = new ServerInfo(server, port, password);
198 serverInfo.setSSL(ssl);
199
200 window = Main.getUI().getServer(this);
201
202 ServerManager.getServerManager().registerServer(this);
203 WindowManager.addWindow(window);
204
205 window.setTitle(server + ":" + port);
206
207 tabCompleter.addEntries(TabCompletionType.COMMAND,
208 AliasWrapper.getAliasWrapper().getAliases());
209 window.getInputHandler().setTabCompleter(tabCompleter);
210
211 updateIcon();
212
213 window.open();
214
215 tabCompleter.addEntries(TabCompletionType.COMMAND,
216 CommandManager.getCommandNames(CommandType.TYPE_SERVER));
217 tabCompleter.addEntries(TabCompletionType.COMMAND,
218 CommandManager.getCommandNames(CommandType.TYPE_GLOBAL));
219
220 this.autochannels = autochannels;
221 this.parserFactory = factory;
222
/*
P/P * Method: void com.dmdirc.Server$1(Server)
*/
223 new Timer("Server Who Timer").schedule(new TimerTask() {
224 @Override
225 public void run() {
/*
P/P * Method: void run()
*
* Preconditions:
* this.channels != null
*
* Presumptions:
* channel.channelInfo@226 != null
* channel.server.parser@226 != null
* channel.server@226 != null
* java.util.Iterator:next(...)@226 != null
* java.util.Map:values(...)@226 != null
*
* Test Vectors:
* java.util.Iterator:hasNext(...)@226: {0}, {1}
*/
226 for (Channel channel : channels.values()) {
227 channel.checkWho();
228 }
229 }
230 }, 0, getConfigManager().getOptionInt(DOMAIN_GENERAL, "whotime"));
231
232 if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "showrawwindow")) {
233 addRaw();
234 }
235
236 connect(server, port, password, ssl, profile);
237 }
238
239 // </editor-fold>
240
241 // <editor-fold defaultstate="collapsed" desc="Connection, disconnection & reconnection">
242
243 /**
244 * Connects to a new server with the specified details.
245 *
246 * @param server The hostname/ip of the server to connect to
247 * @param port The port to connect to
248 * @param password The server password
249 * @param ssl Whether to use SSL or not
250 * @param profile The profile to use
251 */
252 @Precondition({
253 "The IRC Parser is null or not connected",
254 "The specified profile is not null"
255 })
256 @SuppressWarnings("fallthrough")
257 public void connect(final String server, final int port, final String password,
258 final boolean ssl, final Identity profile) {
/*
P/P * Method: void connect(String, int, String, bool, Identity)
*
* Preconditions:
* this.myState.state != null
* profile != null
* this.myState != null
* (soft) init'ed(com.dmdirc.Server$4__static_init.new int[](Server$4__static_init#1)[...])
* (soft) com.dmdirc.actions.CoreActionType__static_init.new CoreActionType(CoreActionType__static_init#17).type != null
* (soft) init'ed(com/dmdirc/actions/ActionManager.killSwitch)
* (soft) this.parser == null
* (soft) this.parserThread != null
* (soft) this.serverInfo != null
* (soft) this...parser != null
* ...
*
* Presumptions:
* com.dmdirc.ServerState:ordinal(...)@262 in {0..6}
* init'ed(com.dmdirc.logger.ErrorLevel.FATAL)
* java.util.Arrays:asList(...)@89 != null
* this...parser@275 != null
* this.eventHandler.owner.server@275 != null
* ...
*
* Postconditions:
* possibly_updated(com/dmdirc/ServerManager.me)
* possibly_updated(this.awayMessage)
* possibly_updated(this.icon)
* this.myState.state not in Addr_Set{null,&com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#5),&com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#7)}
* this.parser in Addr_Set{null,&new IRCParser(getParser#1)}
* possibly_updated(this.parserThread)
* possibly_updated(this.profile)
* init'ed(this.serverInfo)
* new ArrayList(ServerManager#1) num objects == 0, if init'ed
* new IRCParser(getParser#1) num objects <= 1
* ...
*
* Test Vectors:
* com.dmdirc.Server$4__static_init.new int[](Server$4__static_init#1)[...]: {1}, {2}, {3,4}, {5}, {-231..0, 6..232-1}
*/
259 assert profile != null;
260
261 synchronized (myState) {
/*
P/P * Method: com.dmdirc.Server$4__static_init
*
* Preconditions:
* (soft) init'ed(com.dmdirc.ServerState__static_init.new ServerState[](ServerState__static_init#15)[...])
*
* Presumptions:
* com.dmdirc.ServerState:ordinal(...)@262 in {0..6}
*
* Postconditions:
* new int[](Server$4__static_init#1) num objects == 1
*/
262 switch (myState.getState()) {
263 case RECONNECT_WAIT:
264 reconnectTimer.cancel();
265 break;
266 case CLOSING:
267 // Ignore the connection attempt
268 return;
269 case CONNECTED:
270 case CONNECTING:
271 disconnect(getConfigManager().getOption(DOMAIN_GENERAL, "quitmessage"));
272 case DISCONNECTING:
273 while (!myState.getState().isDisconnected()) {
274 try {
275 myState.wait();
276 } catch (InterruptedException ex) {
277 return;
278 }
279 }
280 break;
281 default:
282 // Do nothing
283 break;
284 }
285
286 if (parser != null) {
287 throw new IllegalArgumentException("Connection attempt while parser "
288 + "is still connected.\n\nMy state:" + getState());
289 }
290
291 myState.transition(ServerState.CONNECTING);
292
293 ActionManager.processEvent(CoreActionType.SERVER_CONNECTING, null, this);
294
295 getConfigManager().migrate("", "", server);
296
297 serverInfo = buildServerInfo(server, port, password, ssl);
298
299 this.profile = profile;
300
301 updateIcon();
302
303 addLine("serverConnecting", server, port);
304
305 parser = buildParser();
306
307 doCallbacks();
308
309 awayMessage = null;
310 removeInvites();
311 window.setAwayIndicator(false);
312
313 try {
314 parserThread = new Thread(parser, "IRC Parser thread");
315 parserThread.start();
316 } catch (IllegalThreadStateException ex) {
317 Logger.appError(ErrorLevel.FATAL, "Unable to start IRC Parser", ex);
318 }
319 }
320 }
321
322 /**
323 * Reconnects to the IRC server with a specified reason.
324 *
325 * @param reason The quit reason to send
326 */
327 public void reconnect(final String reason) {
/*
P/P * Method: void reconnect(String)
*
* Preconditions:
* this.myState.state != null
* this.myState != null
* (soft) init'ed(com.dmdirc.Server$4__static_init.new int[](Server$4__static_init#1)[...])
* (soft) init'ed(this.parser)
* (soft) this.parserThread != null
* (soft) init'ed(this.profile)
* (soft) this.serverInfo != null
* (soft) this.channels != null
* (soft) this.config != null
* (soft) this.invites != null
* ...
*
* Presumptions:
* java.util.Arrays:asList(...)@89 init'ed
*
* Postconditions:
* this.awayMessage == old this.awayMessage
* this.icon == One-of{old this.icon, &"secure-server", &"server", &"server-disconnected"}
* this.myState.state == One-of{old this.myState.state, &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#7), &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#9)}
* this.myState.state != null
* this.parser == old this.parser
* init'ed(this.parser)
* this.parserThread == old this.parserThread
* this.parserThread != null
* this.profile == old this.profile
* init'ed(this.profile)
* ...
*/
328 synchronized (myState) {
329 if (myState.getState() == ServerState.CLOSING) {
330 return;
331 }
332
333 disconnect(reason);
334
335 connect(serverInfo.getHost(), serverInfo.getPort(),
336 serverInfo.getPassword(), serverInfo.getSSL(), profile);
337 }
338 }
339
340 /**
341 * Reconnects to the IRC server.
342 */
343 public void reconnect() {
/*
P/P * Method: void reconnect()
*
* Preconditions:
* this.myState.state != null
* this.config != null
* this.myState != null
* (soft) init'ed(com.dmdirc.Server$4__static_init.new int[](Server$4__static_init#1)[...])
* (soft) init'ed(this.parser)
* (soft) this.parserThread != null
* (soft) init'ed(this.profile)
* (soft) this.serverInfo != null
* (soft) this.channels != null
* (soft) this.invites != null
* ...
*
* Postconditions:
* this.awayMessage == old this.awayMessage
* this.icon == One-of{old this.icon, &"secure-server", &"server", &"server-disconnected"}
* this.myState.state == One-of{old this.myState.state, &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#7), &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#9)}
* this.myState.state != null
* this.parser == old this.parser
* init'ed(this.parser)
* this.parserThread == old this.parserThread
* this.parserThread != null
* this.profile == old this.profile
* init'ed(this.profile)
* ...
*/
344 reconnect(getConfigManager().getOption(DOMAIN_GENERAL, "reconnectmessage"));
345 }
346
347 /**
348 * Disconnects from the server with the default quit message.
349 */
350 public void disconnect() {
/*
P/P * Method: void disconnect()
*
* Preconditions:
* this.myState.state != null
* this.config != null
* this.myState != null
* (soft) init'ed(com.dmdirc.Server$4__static_init.new int[](Server$4__static_init#1)[...])
* (soft) this.channels != null
* (soft) this.invites != null
* (soft) this.listeners != null
* (soft) this.myState.history != null
* (soft) this.myState.state.transitions != null
* (soft) init'ed(this.parser)
* ...
*
* Postconditions:
* this.icon == One-of{old this.icon, &"secure-server", &"server", &"server-disconnected"}
* this.myState.state == One-of{old this.myState.state, &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#7), &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#9)}
* this.myState.state != null
*/
351 disconnect(getConfigManager().getOption(DOMAIN_GENERAL, "quitmessage"));
352 }
353
354 /**
355 * Disconnects from the server.
356 *
357 * @param reason disconnect reason
358 */
359 public void disconnect(final String reason) {
/*
P/P * Method: void disconnect(String)
*
* Preconditions:
* this.myState.state != null
* this.myState != null
* (soft) init'ed(com.dmdirc.Server$4__static_init.new int[](Server$4__static_init#1)[...])
* (soft) this.channels != null
* (soft) this.config != null
* (soft) this.invites != null
* (soft) this.listeners != null
* (soft) this.myState.history != null
* (soft) this.myState.state.transitions != null
* (soft) init'ed(this.parser)
* ...
*
* Presumptions:
* com.dmdirc.ServerState:ordinal(...)@361 in {0..6}
*
* Postconditions:
* this.icon == One-of{old this.icon, &"secure-server", &"server", &"server-disconnected"}
* this.myState.state == One-of{old this.myState.state, &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#7), &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#9)}
* this.myState.state != null
*
* Test Vectors:
* com.dmdirc.Server$4__static_init.new int[](Server$4__static_init#1)[...]: {1}, {2, 5..7}, {-231..0, 3,4, 8..232-1}
* this.parser: Inverse{null}, Addr_Set{null}
* com.dmdirc.config.ConfigManager:getOptionBool(...)@388: {0}, {1}
*/
360 synchronized (myState) {
361 switch (myState.getState()) {
362 case CLOSING:
363 case DISCONNECTING:
364 case DISCONNECTED:
365 case TRANSIENTLY_DISCONNECTED:
366 return;
367 case RECONNECT_WAIT:
368 reconnectTimer.cancel();
369 break;
370 default:
371 break;
372 }
373
374 clearChannels();
375
376 if (parser == null) {
377 myState.transition(ServerState.DISCONNECTED);
378 } else {
379 myState.transition(ServerState.DISCONNECTING);
380
381 removeInvites();
382 updateIcon();
383
384 parserThread.interrupt();
385 parser.disconnect(reason);
386 }
387
388 if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
389 "closechannelsonquit")) {
390 closeChannels();
391 }
392
393 if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
394 "closequeriesonquit")) {
395 closeQueries();
396 }
397 }
398 }
399
400 /**
401 * Schedules a reconnect attempt to be performed after a user-defiend delay.
402 */
403 @Precondition("The server state is transiently disconnected")
404 private void doDelayedReconnect() {
/*
P/P * Method: void doDelayedReconnect()
*
* Preconditions:
* this.myState.state != null
* this.config != null
* this.listeners != null
* this.myState != null
* this.myState.history != null
* this.myState.state.transitions != null
* this.serverInfo != null
* (soft) com/dmdirc/Main.controller != null
*
* Postconditions:
* this.icon == &"server-disconnected"
* this.myState.state == &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#11)
* this.reconnectTimer == &new Timer(doDelayedReconnect#4)
* new Timer(doDelayedReconnect#4) num objects == 1
*/
405 synchronized (myState) {
406 if (myState.getState() != ServerState.TRANSIENTLY_DISCONNECTED) {
407 throw new IllegalStateException("doDelayedReconnect when not "
408 + "transiently disconnected\n\nState: " + myState);
409 }
410
411 final int delay = Math.max(1000,
412 getConfigManager().getOptionInt(DOMAIN_GENERAL, "reconnectdelay"));
413
414 handleNotification("connectRetry", getName(), delay / 1000);
415
416 reconnectTimer = new Timer("Server Reconnect Timer");
/*
P/P * Method: void com.dmdirc.Server$2(Server)
*/
417 reconnectTimer.schedule(new TimerTask() {
418 @Override
419 public void run() {
/*
P/P * Method: void run()
*
* Preconditions:
* init'ed(this.myState.state)
* this.myState != null
* (soft) init'ed(com.dmdirc.Server$4__static_init.new int[](Server$4__static_init#1)[...])
* (soft) init'ed(this.parser)
* (soft) this.parserThread != null
* (soft) init'ed(this.profile)
* (soft) this.serverInfo != null
* (soft) this.channels != null
* (soft) this.config != null
* (soft) this.invites != null
* ...
*
* Presumptions:
* java.util.Arrays:asList(...)@89 != null
*
* Postconditions:
* this.awayMessage == old this.awayMessage
* this.icon == One-of{old this.icon, &"server-disconnected"}
* init'ed(this.myState.state)
* this.parser == old this.parser
* init'ed(this.parser)
* this.parserThread == old this.parserThread
* this.profile == old this.profile
* init'ed(this.profile)
* this.serverInfo == old this.serverInfo
*/
420 synchronized (myState) {
421 if (myState.getState() == ServerState.RECONNECT_WAIT) {
422 myState.transition(ServerState.TRANSIENTLY_DISCONNECTED);
423 reconnect();
424 }
425 }
426 }
427 }, delay);
428
429 myState.transition(ServerState.RECONNECT_WAIT);
430 updateIcon();
431 }
432 }
433
434 // </editor-fold>
435
436 // <editor-fold defaultstate="collapsed" desc="Child windows">
437
438 /**
439 * Determines whether the server knows of the specified channel.
440 *
441 * @param channel The channel to be checked
442 * @return True iff the channel is known, false otherwise
443 */
444 public boolean hasChannel(final String channel) {
/*
P/P * Method: bool hasChannel(String)
*
* Preconditions:
* this.channels != null
* this.converter != null
*
* Postconditions:
* init'ed(return_value)
*/
445 return channels.containsKey(converter.toLowerCase(channel));
446 }
447
448 /**
449 * Retrieves the specified channel belonging to this server.
450 *
451 * @param channel The channel to be retrieved
452 * @return The appropriate channel object
453 */
454 public Channel getChannel(final String channel) {
/*
P/P * Method: Channel getChannel(String)
*
* Preconditions:
* this.channels != null
* this.converter != null
*
* Postconditions:
* init'ed(return_value)
*/
455 return channels.get(converter.toLowerCase(channel));
456 }
457
458 /**
459 * Retrieves a list of channel names belonging to this server.
460 *
461 * @return list of channel names belonging to this server
462 */
463 public List<String> getChannels() {
/*
P/P * Method: List getChannels()
*
* Preconditions:
* this.channels != null
*
* Presumptions:
* java.util.Map:keySet(...)@466 != null
*
* Postconditions:
* return_value == &new ArrayList(getChannels#1)
* new ArrayList(getChannels#1) num objects == 1
*
* Test Vectors:
* java.util.Iterator:hasNext(...)@466: {0}, {1}
*/
464 final ArrayList<String> res = new ArrayList<String>();
465
466 for (String channel : channels.keySet()) {
467 res.add(channel);
468 }
469
470 return res;
471 }
472
473 /**
474 * Determines whether the server knows of the specified query.
475 *
476 * @param host The host of the query to look for
477 * @return True iff the query is known, false otherwise
478 */
479 public boolean hasQuery(final String host) {
/*
P/P * Method: bool hasQuery(String)
*
* Preconditions:
* this.queries != null
* (soft) this.converter != null
*
* Presumptions:
* java.util.Iterator:next(...)@482 != null
*
* Postconditions:
* init'ed(return_value)
*
* Test Vectors:
* com.dmdirc.parser.irc.IRCStringConverter:equalsIgnoreCase(...)@483: {0}, {1}
* java.util.Iterator:hasNext(...)@482: {0}, {1}
*/
480 final String nick = ClientInfo.parseHost(host);
481
482 for (Query query : queries) {
483 if (converter.equalsIgnoreCase(ClientInfo.parseHost(query.getHost()), nick)) {
484 return true;
485 }
486 }
487
488 return false;
489 }
490
491 /**
492 * Retrieves the specified query belonging to this server.
493 *
494 * @param host The host of the query to look for
495 * @return The appropriate query object
496 */
497 public Query getQuery(final String host) {
/*
P/P * Method: Query getQuery(String)
*
* Preconditions:
* this.converter != null
* this.queries != null
*
* Presumptions:
* java.util.Iterator:hasNext(...)@500 == 1
* java.util.Iterator:next(...)@500 != null
*
* Postconditions:
* return_value != null
*
* Test Vectors:
* com.dmdirc.parser.irc.IRCStringConverter:equalsIgnoreCase(...)@501: {0}, {1}
*/
498 final String nick = ClientInfo.parseHost(host);
499
500 for (Query query : queries) {
501 if (converter.equalsIgnoreCase(ClientInfo.parseHost(query.getHost()), nick)) {
502 return query;
503 }
504 }
505
506 throw new IllegalArgumentException("No such query: " + host);
507 }
508
509 /**
510 * Retrieves a list of queries belonging to this server.
511 *
512 * @return list of queries belonging to this server
513 */
514 public List<Query> getQueries() {
/*
P/P * Method: List getQueries()
*
* Postconditions:
* return_value == &new ArrayList(getQueries#1)
* new ArrayList(getQueries#1) num objects == 1
*/
515 return new ArrayList<Query>(queries);
516 }
517
518 /**
519 * Adds a raw window to this server.
520 */
521 public void addRaw() {
/*
P/P * Method: void addRaw()
*
* Preconditions:
* init'ed(this.raw)
* (soft) com/dmdirc/Main.controller != null
* (soft) init'ed(this.parser)
* (soft) this.window != null
*
* Postconditions:
* this.raw == One-of{&new Raw(addRaw#1), old this.raw}
* this.raw != null
* new FrameContainer$IconChanger(FrameContainer#2) num objects <= 1
* new ListenerList(FrameContainer#1) num objects <= 1
* new Raw(addRaw#1) num objects <= 1
* new Raw(addRaw#1).changer == &new FrameContainer$IconChanger(FrameContainer#2)
* new Raw(addRaw#1).config != null
* init'ed(new Raw(addRaw#1).icon)
* new Raw(addRaw#1).listeners == &new ListenerList(FrameContainer#1)
* init'ed(new Raw(addRaw#1).notification)
* ...
*
* Test Vectors:
* this.raw: Inverse{null}, Addr_Set{null}
* this.parser: Addr_Set{null}, Inverse{null}
*/
522 if (raw == null) {
523 raw = new Raw(this);
524
525 if (parser != null) {
526 raw.registerCallbacks();
527 }
528 } else {
529 raw.activateFrame();
530 }
531 }
532
533 /**
534 * Retrieves the raw window associated with this server.
535 *
536 * @return The raw window associated with this server.
537 */
538 public Raw getRaw() {
/*
P/P * Method: Raw getRaw()
*
* Preconditions:
* init'ed(this.raw)
*
* Postconditions:
* return_value == this.raw
* init'ed(return_value)
*/
539 return raw;
540 }
541
542 /**
543 * Removes our reference to the raw object (presumably after it has been
544 * closed).
545 */
546 public void delRaw() {
/*
P/P * Method: void delRaw()
*
* Postconditions:
* this.raw == null
*/
547 raw = null; //NOPMD
548 }
549
550 /**
551 * Removes a specific channel and window from this server.
552 *
553 * @param chan channel to remove
554 */
555 public void delChannel(final String chan) {
/*
P/P * Method: void delChannel(String)
*
* Preconditions:
* this.channels != null
* this.converter != null
* this.tabCompleter != null
*
* Presumptions:
* init'ed(com.dmdirc.ui.input.TabCompletionType.CHANNEL)
*/
556 tabCompleter.removeEntry(TabCompletionType.CHANNEL, chan);
557 channels.remove(converter.toLowerCase(chan));
558 }
559
560 /**
561 * Adds a specific channel and window to this server.
562 *
563 * @param chan channel to add
564 */
565 public void addChannel(final ChannelInfo chan) {
/*
P/P * Method: void addChannel(ChannelInfo)
*
* Preconditions:
* this.myState != null
* init'ed(this.myState.state)
* (soft) chan != null
* (soft) this.channels != null
* (soft) this.converter != null
* (soft) this.tabCompleter != null
*
* Presumptions:
* init'ed(com.dmdirc.ui.input.TabCompletionType.CHANNEL)
* getChannel(...).channelInfo@575 != null
* getChannel(...).config@574 != null
* getChannel(...).config@575 != null
* getChannel(...).eventHandler.owner@574 != null
* ...
*
* Test Vectors:
* java.util.Map:containsKey(...)@445: {0}, {1}
*/
566 synchronized (myState) {
567 if (myState.getState() == ServerState.CLOSING) {
568 // Can't join channels while the server is closing
569 return;
570 }
571 }
572
573 if (hasChannel(chan.getName())) {
574 getChannel(chan.getName()).setChannelInfo(chan);
575 getChannel(chan.getName()).selfJoin();
576 } else {
577 final Channel newChan = new Channel(this, chan);
578
579 tabCompleter.addEntry(TabCompletionType.CHANNEL, chan.getName());
580 channels.put(converter.toLowerCase(chan.getName()), newChan);
581 newChan.show();
582 }
583 }
584
585 /**
586 * Adds a query to this server.
587 *
588 * @param host host of the remote client being queried
589 */
590 public void addQuery(final String host) {
/*
P/P * Method: void addQuery(String)
*
* Preconditions:
* this.myState != null
* init'ed(this.myState.state)
* (soft) this.converter != null
* (soft) this.queries != null
* (soft) this.tabCompleter != null
*
* Presumptions:
* init'ed(com.dmdirc.ui.input.TabCompletionType.QUERY_NICK)
*/
591 synchronized (myState) {
592 if (myState.getState() == ServerState.CLOSING) {
593 // Can't open queries while the server is closing
594 return;
595 }
596 }
597
598 if (!hasQuery(host)) {
599 final Query newQuery = new Query(this, host);
600
601 tabCompleter.addEntry(TabCompletionType.QUERY_NICK, ClientInfo.parseHost(host));
602 queries.add(newQuery);
603 }
604 }
605
606 /**
607 * Deletes a query from this server.
608 *
609 * @param query The query that should be removed.
610 */
611 public void delQuery(final Query query) {
/*
P/P * Method: void delQuery(Query)
*
* Preconditions:
* query != null
* init'ed(query.host)
* this.queries != null
* this.tabCompleter != null
*
* Presumptions:
* init'ed(com.dmdirc.ui.input.TabCompletionType.QUERY_NICK)
*/
612 tabCompleter.removeEntry(TabCompletionType.QUERY_NICK, query.getNickname());
613 queries.remove(query);
614 }
615
616 /** {@inheritDoc} */
617 @Override
618 public boolean ownsFrame(final Window target) {
619 // Check if it's our server frame
/*
P/P * Method: bool ownsFrame(Window)
*
* Preconditions:
* init'ed(this.window)
* (soft) this.channels != null
* (soft) this.queries != null
* (soft) init'ed(this.raw)
*
* Presumptions:
* java.util.Iterator:next(...)@624 != null
* java.util.Iterator:next(...)@628 != null
* java.util.Map:values(...)@624 != null
*
* Postconditions:
* init'ed(return_value)
*
* Test Vectors:
* this.raw: Addr_Set{null}, Inverse{null}
* this.window: Addr_Set{null}, Inverse{null}
* java.lang.Object:equals(...)@215: {0}, {1}
* java.lang.Object:equals(...)@620: {0}, {1}
* java.util.Iterator:hasNext(...)@624: {0}, {1}
* java.util.Iterator:hasNext(...)@628: {0}, {1}
*/
620 if (window != null && window.equals(target)) { return true; }
621 // Check if it's the raw frame
622 if (raw != null && raw.ownsFrame(target)) { return true; }
623 // Check if it's a channel frame
624 for (Channel channel : channels.values()) {
625 if (channel.ownsFrame(target)) { return true; }
626 }
627 // Check if it's a query frame
628 for (Query query : queries) {
629 if (query.ownsFrame(target)) { return true; }
630 }
631 return false;
632 }
633
634 /**
635 * Sets the specified frame as the most-recently activated.
636 *
637 * @param source The frame that was activated
638 */
639 public void setActiveFrame(final FrameContainer source) {
/*
P/P * Method: void setActiveFrame(FrameContainer)
*
* Postconditions:
* this.activeFrame == source
* init'ed(this.activeFrame)
*/
640 activeFrame = source;
641 }
642
643 /**
644 * Retrieves a list of all children of this server instance.
645 *
646 * @return A list of this server's children
647 */
648 public List<WritableFrameContainer> getChildren() {
/*
P/P * Method: List getChildren()
*
* Preconditions:
* this.channels != null
* init'ed(this.raw)
*
* Postconditions:
* return_value == &new ArrayList(getChildren#1)
* new ArrayList(getChildren#1) num objects == 1
*
* Test Vectors:
* this.raw: Addr_Set{null}, Inverse{null}
*/
649 final List<WritableFrameContainer> res = new ArrayList<WritableFrameContainer>();
650
651 if (raw != null) {
652 res.add(raw);
653 }
654
655 res.addAll(channels.values());
656 res.addAll(queries);
657
658 return res;
659 }
660
661 /**
662 * Closes all open channel windows associated with this server.
663 */
664 private void closeChannels() {
/*
P/P * Method: void closeChannels()
*
* Preconditions:
* this.channels != null
*
* Presumptions:
* java.util.ArrayList:iterator(...)@665 != null
* java.util.Iterator:next(...)@665 != null
*
* Test Vectors:
* java.util.Iterator:hasNext(...)@665: {0}, {1}
*/
665 for (Channel channel : new ArrayList<Channel>(channels.values())) {
666 channel.close();
667 }
668 }
669
670 /**
671 * Clears the nicklist of all open channels.
672 */
673 private void clearChannels() {
/*
P/P * Method: void clearChannels()
*
* Preconditions:
* this.channels != null
*
* Presumptions:
* channel.config@674 != null
* channel.listeners@674 != null
* channel.window@674 != null
* java.util.Iterator:next(...)@674 != null
* java.util.Map:values(...)@674 != null
*
* Test Vectors:
* java.util.Iterator:hasNext(...)@674: {0}, {1}
*/
674 for (Channel channel : channels.values()) {
675 channel.resetWindow();
676 }
677 }
678
679 /**
680 * Closes all open query windows associated with this server.
681 */
682 private void closeQueries() {
/*
P/P * Method: void closeQueries()
*
* Presumptions:
* java.util.ArrayList:iterator(...)@683 != null
* java.util.Iterator:next(...)@683 != null
*
* Test Vectors:
* java.util.Iterator:hasNext(...)@683: {0}, {1}
*/
683 for (Query query : new ArrayList<Query>(queries)) {
684 query.close();
685 }
686 }
687
688 // </editor-fold>
689
690 // <editor-fold defaultstate="collapsed" desc="Miscellaneous methods">
691
692 /**
693 * Construsts a {@link ServerInfo} object for the specified details.
694 *
695 * @param server The hostname or IP address of the server
696 * @param port The port of the server
697 * @param password The password to use, if any
698 * @param ssl Whether or not to use SSL
699 * @return An appropriately configured ServerInfo instance
700 */
701 private ServerInfo buildServerInfo(final String server, final int port,
702 final String password, final boolean ssl) {
/*
P/P * Method: ServerInfo buildServerInfo(String, int, String, bool)
*
* Preconditions:
* this.config != null
*
* Postconditions:
* return_value == &new ServerInfo(buildServerInfo#1)
* new ServerInfo(buildServerInfo#1) num objects == 1
*
* Test Vectors:
* com.dmdirc.config.ConfigManager:hasOptionString(...)@706: {0}, {1}
*/
703 final ServerInfo myInfo = new ServerInfo(server, port, password);
704 myInfo.setSSL(ssl);
705
706 if (getConfigManager().hasOptionString(DOMAIN_SERVER, "proxy.address")) {
707 myInfo.setUseSocks(true);
708
709 myInfo.setProxyHost(getConfigManager()
710 .getOption(DOMAIN_SERVER, "proxy.address"));
711 myInfo.setProxyUser(getConfigManager()
712 .getOption(DOMAIN_SERVER, "proxy.user"));
713 myInfo.setProxyPass(getConfigManager()
714 .getOption(DOMAIN_SERVER, "proxy.password"));
715 myInfo.setProxyPort(getConfigManager()
716 .getOptionInt(DOMAIN_SERVER, "proxy.port"));
717 }
718
719 return myInfo;
720 }
721
722 /**
723 * Builds an appropriately configured {@link IRCParser} for this server.
724 *
725 * @return A configured IRC parser.
726 */
727 private IRCParser buildParser() {
/*
P/P * Method: IRCParser buildParser()
*
* Preconditions:
* this.config != null
* this.parserFactory != null
* this.profile != null
* this.serverInfo != null
*
* Presumptions:
* com.dmdirc.config.ConfigManager:getOptionInt(...)@740/com.dmdirc.parser.irc.IRCParser:getPingTimerLength(...)@740 in range
* com.dmdirc.config.ConfigManager:getOptionInt(...)@740/com.dmdirc.parser.irc.IRCParser:getPingTimerLength(...)@740 in {-231..232-1}
* com.dmdirc.parser.irc.IRCParser:getPingTimerLength(...)@740 != 0
*
* Postconditions:
* return_value == &new IRCParser(getParser#1)
* new IRCParser(getParser#1) num objects == 1
*
* Test Vectors:
* com.dmdirc.config.ConfigManager:hasOptionString(...)@743: {0}, {1}
*/
728 final CertificateManager certManager = new CertificateManager(serverInfo.getHost(),
729 getConfigManager());
730
731 final MyInfo myInfo = buildMyInfo();
732 final IRCParser myParser = parserFactory.getParser(myInfo, serverInfo);
733 myParser.setTrustManager(new TrustManager[]{certManager});
734 myParser.setKeyManagers(certManager.getKeyManager());
735 myParser.setRemoveAfterCallback(true);
736 myParser.setCreateFake(true);
737 myParser.setIgnoreList(ignoreList);
738 myParser.setPingTimerLength(getConfigManager().getOptionInt(DOMAIN_SERVER,
739 "pingtimer"));
740 myParser.setPingCountDownLength((int) (getConfigManager().getOptionInt(DOMAIN_SERVER,
741 "pingfrequency") / myParser.getPingTimerLength()));
742
743 if (getConfigManager().hasOptionString(DOMAIN_GENERAL, "bindip")) {
744 myParser.setBindIP(getConfigManager().getOption(DOMAIN_GENERAL, "bindip"));
745 }
746
747 return myParser;
748 }
749
750 /**
751 * Retrieves the MyInfo object used for the IRC Parser.
752 *
753 * @return The MyInfo object for our profile
754 */
755 @Precondition({
756 "The current profile is not null",
757 "The current profile specifies at least one nickname"
758 })
759 private MyInfo buildMyInfo() {
/*
P/P * Method: MyInfo buildMyInfo()
*
* Preconditions:
* this.profile != null
*
* Presumptions:
* com.dmdirc.config.Identity:getOptionList(...)@761 != null
* com.dmdirc.config.Identity:getOptionList(...)@764 != null
*
* Postconditions:
* return_value == &new MyInfo(buildMyInfo#1)
* new MyInfo(buildMyInfo#1) num objects == 1
*
* Test Vectors:
* com.dmdirc.config.Identity:hasOptionString(...)@767: {0}, {1}
*/
760 Logger.assertTrue(profile != null);
761 Logger.assertTrue(!profile.getOptionList(DOMAIN_PROFILE, "nicknames").isEmpty());
762
763 final MyInfo myInfo = new MyInfo();
764 myInfo.setNickname(profile.getOptionList(DOMAIN_PROFILE, "nicknames").get(0));
765 myInfo.setRealname(profile.getOption(DOMAIN_PROFILE, "realname"));
766
767 if (profile.hasOptionString(DOMAIN_PROFILE, "ident")) {
768 myInfo.setUsername(profile.getOption(DOMAIN_PROFILE, "ident"));
769 }
770
771 return myInfo;
772 }
773
774 /**
775 * Updates this server's icon.
776 */
777 private void updateIcon() {
/*
P/P * Method: void updateIcon()
*
* Preconditions:
* this.config != null
* this.listeners != null
* this.myState != null
* init'ed(this.myState.state)
* (soft) this.serverInfo != null
*
* Postconditions:
* this.icon == One-of{&"secure-server", &"server", &"server-disconnected"}
* this.icon in Addr_Set{&"server-disconnected",&"server",&"secure-server"}
*/
778 final String icon = myState.getState() == ServerState.CONNECTED
779 ? serverInfo.getSSL() ? "secure-server" : "server"
780 : "server-disconnected";
781 setIcon(icon);
782 }
783
784 /**
785 * Registers callbacks.
786 */
787 private void doCallbacks() {
/*
P/P * Method: void doCallbacks()
*
* Preconditions:
* this.eventHandler != null
* this.eventHandler.owner != null
* this.queries != null
* init'ed(this.raw)
* (soft) this...parser != null
* (soft) this.eventHandler.owner.server != null
* (soft) this.raw.server != null
*
* Presumptions:
* java.util.Iterator:next(...)@794 != null
* query.server.parser@794 != null
* query.server@794 != null
*
* Test Vectors:
* this.raw: Addr_Set{null}, Inverse{null}
* java.util.Iterator:hasNext(...)@794: {0}, {1}
*/
788 if (raw != null) {
789 raw.registerCallbacks();
790 }
791
792 eventHandler.registerCallbacks();
793
794 for (Query query : queries) {
795 query.reregister();
796 }
797 }
798
799 /**
800 * Joins the specified channel, or adds it to the auto-join list if the
801 * server is not connected.
802 *
803 * @param channel The channel to be joined
804 */
805 public void join(final String channel) {
/*
P/P * Method: void join(String)
*
* Preconditions:
* this.myState != null
* init'ed(this.myState.state)
* (soft) this.autochannels != null
* (soft) this.channels != null
* (soft) this.converter != null
* (soft) this.invites != null
* (soft) this.listeners != null
* (soft) this.parser != null
*
* Presumptions:
* getChannel(...).channelInfo@811 != null
* getChannel(...).config@811 != null
* getChannel(...).listeners@811 != null
* getChannel(...).server.parser@811 != null
* getChannel(...).server@811 != null
* ...
*
* Test Vectors:
* java.util.Map:containsKey(...)@445: {0}, {1}
*/
806 synchronized (myState) {
807 if (myState.getState() == ServerState.CONNECTED) {
808 removeInvites(channel);
809
810 if (hasChannel(channel)) {
811 getChannel(channel).join();
812 getChannel(channel).activateFrame();
813 } else {
814 parser.joinChannel(channel);
815 }
816 } else {
817 autochannels.add(channel);
818 }
819 }
820 }
821
822 /** {@inheritDoc} */
823 @Override
824 public void sendLine(final String line) {
/*
P/P * Method: void sendLine(String)
*
* Preconditions:
* init'ed(this.parser)
* (soft) this.myState != null
* (soft) init'ed(this.myState.state)
* (soft) this.window != null
*
* Presumptions:
* com.dmdirc.ui.interfaces.ServerWindow:getTranscoder(...)@827 != null
*/
825 synchronized (myState) {
826 if (parser != null && myState.getState() == ServerState.CONNECTED) {
827 parser.sendLine(window.getTranscoder().encode(line));
828 }
829 }
830 }
831
832 /** {@inheritDoc} */
833 @Override
834 public int getMaxLineLength() {
/*
P/P * Method: int getMaxLineLength()
*
* Postconditions:
* return_value == 510
*/
835 return IRCParser.MAX_LINELENGTH;
836 }
837
838 /**
839 * Retrieves the parser used for this connection.
840 *
841 * @return IRCParser this connection's parser
842 */
843 public IRCParser getParser() {
/*
P/P * Method: IRCParser getParser()
*
* Preconditions:
* init'ed(this.parser)
*
* Postconditions:
* return_value == this.parser
* init'ed(return_value)
*/
844 return parser;
845 }
846
847 /**
848 * Retrieves the profile that's in use for this server.
849 *
850 * @return The profile in use by this server
851 */
852 public Identity getProfile() {
/*
P/P * Method: Identity getProfile()
*
* Preconditions:
* init'ed(this.profile)
*
* Postconditions:
* return_value == this.profile
* init'ed(return_value)
*/
853 return profile;
854 }
855
856 /**
857 * Retrieves the name of this server.
858 *
859 * @return The name of this server
860 */
861 public String getName() {
/*
P/P * Method: String getName()
*
* Preconditions:
* this.serverInfo != null
*
* Postconditions:
* init'ed(return_value)
*/
862 return serverInfo.getHost();
863 }
864
865 /**
866 * Retrieves the name of this server's network. The network name is
867 * determined using the following rules:
868 *
869 * 1. If the server includes its network name in the 005 information, we
870 * use that
871 * 2. If the server's name ends in biz, com, info, net or org, we use the
872 * second level domain (e.g., foo.com)
873 * 3. If the server's name contains more than two dots, we drop everything
874 * up to and including the first part, and use the remainder
875 * 4. In all other cases, we use the full server name
876 *
877 * @return The name of this server's network
878 */
879 public String getNetwork() {
/*
P/P * Method: String getNetwork()
*
* Preconditions:
* this.parser != null
*
* Presumptions:
* com.dmdirc.parser.irc.IRCParser:getNetworkName(...)@883 != null
* com.dmdirc.parser.irc.IRCParser:getServerName(...)@884 != null
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* init'ed(return_value)
*
* Test Vectors:
* java.lang.String:isEmpty(...)@883: {0}, {1}
*/
880 if (parser == null) {
881 throw new IllegalStateException("getNetwork called when "
882 + "parser is null (state: " + getState() + ")");
883 } else if (parser.getNetworkName().isEmpty()) {
884 return getNetworkFromServerName(parser.getServerName());
885 } else {
886 return parser.getNetworkName();
887 }
888 }
889
890 /**
891 * Determines whether this server is currently connected to the specified
892 * network.
893 *
894 * @param target The network to check for
895 * @return True if this server is connected to the network, false otherwise
896 * @since 0.6.3m1rc3
897 */
898 public boolean isNetwork(String target) {
/*
P/P * Method: bool isNetwork(String)
*
* Preconditions:
* init'ed(this.parser)
*
* Postconditions:
* init'ed(return_value)
*
* Test Vectors:
* this.parser: Inverse{null}, Addr_Set{null}
*/
899 synchronized (myState) {
900 if (parser == null) {
901 return false;
902 } else {
903 return getNetwork().equalsIgnoreCase(target);
904 }
905 }
906 }
907
908 /**
909 * Calculates a network name from the specified server name. This method
910 * implements parts 2-4 of the procedure documented at getNetwork().
911 *
912 * @param serverName The server name to parse
913 * @return A network name for the specified server
914 */
915 protected static String getNetworkFromServerName(final String serverName) {
/*
P/P * Method: String getNetworkFromServerName(String)
*
* Preconditions:
* serverName != null
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* return_value == One-of{&java.lang.StringBuilder:toString(...), serverName}
* return_value != null
*
* Test Vectors:
* java.lang.String:endsWith(...)@921: {0}, {1}
*/
916 final String[] parts = serverName.split("\\.");
917 final String[] tlds = {"biz", "com", "info", "net", "org"};
918 boolean isTLD = false;
919
920 for (String tld : tlds) {
921 if (serverName.endsWith("." + tld)) {
922 isTLD = true;
923 }
924 }
925
926 if (isTLD && parts.length > 2) {
927 return parts[parts.length - 2] + "." + parts[parts.length - 1];
928 } else if (parts.length > 2) {
929 final StringBuilder network = new StringBuilder();
930
931 for (int i = 1; i < parts.length; i++) {
932 if (network.length() > 0) {
933 network.append('.');
934 }
935
936 network.append(parts[i]);
937 }
938
939 return network.toString();
940 } else {
941 return serverName;
942 }
943 }
944
945 /**
946 * Retrieves the name of this server's IRCd.
947 *
948 * @return The name of this server's IRCd
949 */
950 public String getIrcd() {
/*
P/P * Method: String getIrcd()
*
* Preconditions:
* this.parser != null
*
* Postconditions:
* init'ed(return_value)
*/
951 return parser.getIRCD(true);
952 }
953
954 /**
955 * Returns the current away status.
956 *
957 * @return True if the client is marked as away, false otherwise
958 */
959 public boolean isAway() {
/*
P/P * Method: bool isAway()
*
* Preconditions:
* init'ed(this.awayMessage)
*
* Postconditions:
* init'ed(return_value)
*/
960 return awayMessage != null;
961 }
962
963 /**
964 * Gets the current away message.
965 *
966 * @return Null if the client isn't away, or a textual away message if it is
967 */
968 public String getAwayMessage() {
/*
P/P * Method: String getAwayMessage()
*
* Preconditions:
* init'ed(this.awayMessage)
*
* Postconditions:
* return_value == this.awayMessage
* init'ed(return_value)
*/
969 return awayMessage;
970 }
971
972 /**
973 * Returns the tab completer for this connection.
974 *
975 * @return The tab completer for this server
976 */
977 public TabCompleter getTabCompleter() {
/*
P/P * Method: TabCompleter getTabCompleter()
*
* Postconditions:
* return_value == this.tabCompleter
* init'ed(return_value)
*/
978 return tabCompleter;
979 }
980
981 /** {@inheritDoc} */
982 @Override
983 public InputWindow getFrame() {
/*
P/P * Method: InputWindow getFrame()
*
* Preconditions:
* init'ed(this.window)
*
* Postconditions:
* return_value == this.window
* init'ed(return_value)
*/
984 return window;
985 }
986
987 /**
988 * Retrieves the current state for this server.
989 *
990 * @return This server's state
991 */
992 public ServerState getState() {
/*
P/P * Method: ServerState getState()
*
* Preconditions:
* this.myState != null
* init'ed(this.myState.state)
*
* Postconditions:
* return_value == this.myState.state
* init'ed(return_value)
*/
993 return myState.getState();
994 }
995
996 /**
997 * Retrieves the status object for this server. Effecting state transitions
998 * on the object returned by this method will almost certainly cause
999 * problems.
1000 *
1001 * @since 0.6.3m1
1002 * @return This server's status object.
1003 */
1004 public ServerStatus getStatus() {
/*
P/P * Method: ServerStatus getStatus()
*
* Postconditions:
* return_value == this.myState
* init'ed(return_value)
*/
1005 return myState;
1006 }
1007
1008 /** {@inheritDoc} */
1009 @Override
1010 public void windowClosing() {
/*
P/P * Method: void windowClosing()
*
* Preconditions:
* init'ed(com/dmdirc/ServerManager.me)
* init'ed(this.parser)
* this.window != null
* this.channels != null
* this.eventHandler != null
* this.eventHandler.owner != null
* this.myState != null
* this.myState.history != null
* init'ed(this.raw)
* (soft) init'ed(com.dmdirc.Server$4__static_init.new int[](Server$4__static_init#1)[...])
* ...
*
* Presumptions:
* getServerManager(...).servers != null
* java.util.Arrays:asList(...)@89 != null
*
* Postconditions:
* com/dmdirc/ServerManager.me == One-of{old com/dmdirc/ServerManager.me, &new ServerManager(getServerManager#1)}
* com/dmdirc/ServerManager.me != null
* this.icon == One-of{old this.icon, &"secure-server", &"server", &"server-disconnected"}
* this.myState.state == &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#13)
* this.parser == null
* this.window == null
* new ArrayList(ServerManager#1) num objects <= 1
* new ServerManager(getServerManager#1) num objects <= 1
* new ServerManager(getServerManager#1).servers == &new ArrayList(ServerManager#1)
*
* Test Vectors:
* this.parser: Addr_Set{null}, Inverse{null}
* this.raw: Addr_Set{null}, Inverse{null}
* com.dmdirc.parser.irc.IRCParser:isReady(...)@1019: {0}, {1}
*/
1011 synchronized (myState) {
1012 // 1: Make the window non-visible
1013 window.setVisible(false);
1014
1015 // 2: Remove any callbacks or listeners
1016 eventHandler.unregisterCallbacks();
1017
1018 // 3: Trigger any actions neccessary
1019 if (parser != null && parser.isReady()) {
1020 disconnect();
1021 }
1022
1023 myState.transition(ServerState.CLOSING);
1024 }
1025
1026 closeChannels();
1027 closeQueries();
1028 removeInvites();
1029
1030 if (raw != null) {
1031 raw.close();
1032 }
1033
1034 // 4: Trigger action for the window closing
1035 // 5: Inform any parents that the window is closing
1036 ServerManager.getServerManager().unregisterServer(this);
1037
1038 // 6: Remove the window from the window manager
1039 WindowManager.removeWindow(window);
1040
1041 // 7: Remove any references to the window and parents
1042 window = null; //NOPMD
1043 parser = null; //NOPMD
1044 }
1045
1046 /**
1047 * Passes the arguments to the most recently activated frame for this
1048 * server. If the frame isn't know, or isn't visible, use this frame
1049 * instead.
1050 *
1051 * @param messageType The type of message to send
1052 * @param args The arguments for the message
1053 */
1054 public void addLineToActive(final String messageType, final Object... args) {
/*
P/P * Method: void addLineToActive(String, Object[])
*
* Preconditions:
* init'ed(this.activeFrame)
*
* Presumptions:
* getFrame(...)@1055 != null
* getFrame(...)@1059 != null
*
* Postconditions:
* this.activeFrame == One-of{old this.activeFrame, this}
* this.activeFrame != null
*
* Test Vectors:
* this.activeFrame: Addr_Set{null}, Inverse{null}
* com.dmdirc.ui.interfaces.Window:isVisible(...)@1055: {1}, {0}
*/
1055 if (activeFrame == null || !activeFrame.getFrame().isVisible()) {
1056 activeFrame = this;
1057 }
1058
1059 activeFrame.getFrame().addLine(messageType, args);
1060 }
1061
1062 /**
1063 * Passes the arguments to all frames for this server.
1064 *
1065 * @param messageType The type of message to send
1066 * @param args The arguments of the message
1067 */
1068 public void addLineToAll(final String messageType, final Object... args) {
/*
P/P * Method: void addLineToAll(String, Object[])
*
* Preconditions:
* this.channels != null
* this.queries != null
*
* Presumptions:
* channel.window@1069 != null
* java.util.Iterator:next(...)@1069 != null
* java.util.Iterator:next(...)@1073 != null
* java.util.Map:values(...)@1069 != null
* query.window@1073 != null
*
* Test Vectors:
* java.util.Iterator:hasNext(...)@1069: {0}, {1}
* java.util.Iterator:hasNext(...)@1073: {0}, {1}
*/
1069 for (Channel channel : channels.values()) {
1070 channel.getFrame().addLine(messageType, args);
1071 }
1072
1073 for (Query query : queries) {
1074 query.getFrame().addLine(messageType, args);
1075 }
1076
1077 addLine(messageType, args);
1078 }
1079
1080 /**
1081 * Replies to an incoming CTCP message.
1082 *
1083 * @param source The source of the message
1084 * @param type The CTCP type
1085 * @param args The CTCP arguments
1086 */
1087 public void sendCTCPReply(final String source, final String type, final String args) {
/*
P/P * Method: void sendCTCPReply(String, String, String)
*
* Preconditions:
* type != null
* (soft) this.config != null
* (soft) this.parser != null
*
* Test Vectors:
* java.lang.String:equalsIgnoreCase(...)@1088: {0}, {1}
* java.lang.String:equalsIgnoreCase(...)@1092: {0}, {1}
* java.lang.String:equalsIgnoreCase(...)@1094: {0}, {1}
*/
1088 if (type.equalsIgnoreCase("VERSION")) {
1089 parser.sendCTCPReply(source, "VERSION", "DMDirc " +
1090 getConfigManager().getOption("version", "version")
1091 + " - http://www.dmdirc.com/");
1092 } else if (type.equalsIgnoreCase("PING")) {
1093 parser.sendCTCPReply(source, "PING", args);
1094 } else if (type.equalsIgnoreCase("CLIENTINFO")) {
1095 parser.sendCTCPReply(source, "CLIENTINFO", "VERSION PING CLIENTINFO");
1096 }
1097 }
1098
1099 /**
1100 * Determines if the specified channel name is valid. A channel name is
1101 * valid if we already have an existing Channel with the same name, or
1102 * we have a valid parser instance and the parser says it's valid.
1103 *
1104 * @param channelName The name of the channel to test
1105 * @return True if the channel name is valid, false otherwise
1106 */
1107 public boolean isValidChannelName(final String channelName) {
/*
P/P * Method: bool isValidChannelName(String)
*
* Preconditions:
* this.channels != null
* this.converter != null
* (soft) init'ed(this.parser)
*
* Postconditions:
* init'ed(return_value)
*/
1108 return hasChannel(channelName)
1109 || (parser != null && parser.isValidChannelName(channelName));
1110 }
1111
1112 /**
1113 * Returns this server's name.
1114 *
1115 * @return A string representation of this server (i.e., its name)
1116 */
1117 @Override
1118 public String toString() {
/*
P/P * Method: String toString()
*
* Preconditions:
* this.serverInfo != null
*
* Postconditions:
* init'ed(return_value)
*/
1119 return getName();
1120 }
1121
1122 /**
1123 * Returns the server instance associated with this frame.
1124 *
1125 * @return the associated server connection
1126 */
1127 @Override
1128 public Server getServer() {
/*
P/P * Method: Server getServer()
*
* Postconditions:
* return_value == this
* return_value != null
*/
1129 return this;
1130 }
1131
1132 /** {@inheritDoc} */
1133 @Override
1134 protected boolean processNotificationArg(final Object arg, final List<Object> args) {
/*
P/P * Method: bool processNotificationArg(Object, List)
*
* Preconditions:
* (soft) args != null
*
* Postconditions:
* init'ed(return_value)
*/
1135 if (arg instanceof ClientInfo) {
1136 final ClientInfo clientInfo = (ClientInfo) arg;
1137 args.add(clientInfo.getNickname());
1138 args.add(clientInfo.getIdent());
1139 args.add(clientInfo.getHost());
1140 return true;
1141 } else {
1142 return super.processNotificationArg(arg, args);
1143 }
1144 }
1145
1146 // </editor-fold>
1147
1148 // <editor-fold defaultstate="collapsed" desc="Parser callbacks">
1149
1150 /**
1151 * Called when the server says that the nickname we're trying to use is
1152 * already in use.
1153 *
1154 * @param nickname The nickname that we were trying to use
1155 */
1156 public void onNickInUse(final String nickname) {
/*
P/P * Method: void onNickInUse(String)
*
* Preconditions:
* this.converter != null
* this.parser != null
* (soft) this.profile != null
*
* Presumptions:
* (int) (java.lang.Math:random(...)@1164*10) in {-231..232-1}
* com.dmdirc.config.Identity:getOptionList(...)@1166 != null
* java.util.List:get(...)@1177 != null
*
* Test Vectors:
* com.dmdirc.parser.irc.IRCStringConverter:equalsIgnoreCase(...)@1160: {1}, {0}
* java.lang.String:isEmpty(...)@1177: {1}, {0}
* java.util.Iterator:hasNext(...)@1170: {0}, {1}
*/
1157 final String lastNick = parser.getMyNickname();
1158
1159 // If our last nick is still valid, ignore the in use message
1160 if (!converter.equalsIgnoreCase(lastNick, nickname)) {
1161 return;
1162 }
1163
1164 String newNick = lastNick + (int) (Math.random() * 10);
1165
1166 final List<String> alts = profile.getOptionList(DOMAIN_PROFILE, "nicknames");
1167 int offset = 0;
1168
1169 // Loop so we can check case sensitivity
1170 for (String alt : alts) {
1171 offset++;
1172 if (converter.equalsIgnoreCase(alt, lastNick)) {
1173 break;
1174 }
1175 }
1176
1177 if (offset < alts.size() && !alts.get(offset).isEmpty()) {
1178 newNick = alts.get(offset);
1179 }
1180
1181 parser.setNickname(newNick);
1182 }
1183
1184 /**
1185 * Called when the server sends a numeric event.
1186 *
1187 * @param numeric The numeric code for the event
1188 * @param tokens The (tokenised) arguments of the event
1189 */
1190 public void onNumeric(final int numeric, final String[] tokens) {
/*
P/P * Method: void onNumeric(int, String[])
*
* Preconditions:
* init'ed(com/dmdirc/actions/ActionManager.killSwitch)
* this.config != null
* this.parser != null
* (soft) com.dmdirc.actions.CoreActionType__static_init.new CoreActionType(CoreActionType__static_init#15).type != null
* (soft) com/dmdirc/Main.controller != null
* (soft) init'ed(com/dmdirc/ServerManager.me)
* (soft) tokens != null
* (soft) tokens.length <= 232-1
* (soft) init'ed(tokens[...])
*
* Postconditions:
* com/dmdirc/ServerManager.me == old com/dmdirc/ServerManager.me
* new ArrayList(ServerManager#1) num objects == undefined
* new ArrayList(ServerManager#1) num objects == 0, if init'ed
* new ServerManager(getServerManager#1) num objects == new ArrayList(ServerManager#1) num objects
* new ServerManager(getServerManager#1).servers == undefined
* new ServerManager(getServerManager#1).servers == null
*
* Test Vectors:
* numeric: {100..232-1}, {-231..9}, {10..99}
* com.dmdirc.config.ConfigManager:hasOptionString(...)@1203: {0}, {1}
* com.dmdirc.config.ConfigManager:hasOptionString(...)@1205: {0}, {1}
* com.dmdirc.config.ConfigManager:hasOptionString(...)@1207: {0}, {1}
*/
1191 String snumeric = String.valueOf(numeric);
1192
1193 if (numeric < 10) {
1194 snumeric = "00" + snumeric;
1195 } else if (numeric < 100) {
1196 snumeric = "0" + snumeric;
1197 }
1198
1199 final String withIrcd = "numeric_" + parser.getIRCD(true) + "_" + snumeric;
1200 final String sansIrcd = "numeric_" + snumeric;
1201 StringBuffer target = null;
1202
1203 if (getConfigManager().hasOptionString("formatter", withIrcd)) {
1204 target = new StringBuffer(withIrcd);
1205 } else if (getConfigManager().hasOptionString("formatter", sansIrcd)) {
1206 target = new StringBuffer(sansIrcd);
1207 } else if (getConfigManager().hasOptionString("formatter", "numeric_unknown")) {
1208 target = new StringBuffer("numeric_unknown");
1209 }
1210
1211 ActionManager.processEvent(CoreActionType.SERVER_NUMERIC, target, this,
1212 Integer.valueOf(numeric), tokens);
1213
1214 if (target != null) {
1215 handleNotification(target.toString(), (Object[]) tokens);
1216 }
1217 }
1218
1219 /**
1220 * Called when the socket has been closed.
1221 */
1222 public void onSocketClosed() {
/*
P/P * Method: void onSocketClosed()
*
* Preconditions:
* (soft) com.dmdirc.actions.CoreActionType__static_init.new CoreActionType(CoreActionType__static_init#19).type != null
* (soft) com/dmdirc/Main.controller != null
* (soft) init'ed(com/dmdirc/actions/ActionManager.killSwitch)
* (soft) init'ed(com/dmdirc/ServerManager.me)
* (soft) init'ed(this.awayMessage)
* (soft) this.myState.state != null
* (soft) this.channels != null
* (soft) this.config != null
* (soft) this.eventHandler != null
* (soft) this.eventHandler.owner != null
* ...
*
* Presumptions:
* java.util.Arrays:asList(...)@89 init'ed
*
* Postconditions:
* com/dmdirc/ServerManager.me == old com/dmdirc/ServerManager.me
* this.awayMessage == One-of{old this.awayMessage, null}
* init'ed(this.awayMessage)
* this.icon == One-of{old this.icon, &"secure-server", &"server", &"server-disconnected"}
* this.myState.state == One-of{old this.myState.state, &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#7), &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#5)}
* this.myState.state != null
* this.parser == One-of{old this.parser, null}
* this.reconnectTimer == old this.reconnectTimer
* new ArrayList(ServerManager#1) num objects == 0, if init'ed
* new ServerManager(getServerManager#1) num objects == 0, if init'ed
* ...
*
* Test Vectors:
* com.dmdirc.config.ConfigManager:getOptionBool(...)@1259: {0}, {1}
* com.dmdirc.config.ConfigManager:getOptionBool(...)@1264: {0}, {1}
* java.lang.Thread:holdsLock(...)@1223: {0}, {1}
*/
1223 if (Thread.holdsLock(myState)) {
/*
P/P * Method: void com.dmdirc.Server$3(Server)
*/
1224 new Thread(new Runnable() {
1225 /** {@inheritDoc} */
1226 @Override
1227 public void run() {
/*
P/P * Method: void run()
*/
1228 onSocketClosed();
1229 }
1230 }, "Socket closed deferred thread").start();
1231 return;
1232 }
1233
1234 handleNotification("socketClosed", getName());
1235
1236 ActionManager.processEvent(CoreActionType.SERVER_DISCONNECTED, null, this);
1237
1238 eventHandler.unregisterCallbacks();
1239
1240 synchronized (myState) {
1241 if (myState.getState() == ServerState.CLOSING
1242 || myState.getState() == ServerState.DISCONNECTED) {
1243 // This has been triggered via .disconect()
1244 return;
1245 }
1246
1247 if (myState.getState() == ServerState.DISCONNECTING) {
1248 myState.transition(ServerState.DISCONNECTED);
1249 } else {
1250 myState.transition(ServerState.TRANSIENTLY_DISCONNECTED);
1251 }
1252
1253 clearChannels();
1254
1255 parser = null;
1256
1257 updateIcon();
1258
1259 if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
1260 "closechannelsondisconnect")) {
1261 closeChannels();
1262 }
1263
1264 if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
1265 "closequeriesondisconnect")) {
1266 closeQueries();
1267 }
1268
1269 removeInvites();
1270 updateAwayState(null);
1271
1272 if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
1273 "reconnectondisconnect")
1274 && myState.getState() == ServerState.TRANSIENTLY_DISCONNECTED) {
1275 doDelayedReconnect();
1276 }
1277 }
1278 }
1279
1280 /**
1281 * Called when an error was encountered while connecting.
1282 *
1283 * @param errorInfo The parser's error information
1284 */
1285 @Precondition("The current server state is CONNECTING")
1286 public void onConnectError(final ParserError errorInfo) {
/*
P/P * Method: void onConnectError(ParserError)
*
* Preconditions:
* this.myState.state != null
* this.myState != null
* (soft) com.dmdirc.actions.CoreActionType__static_init.new CoreActionType(CoreActionType__static_init#18).type != null
* (soft) com/dmdirc/Main.controller != null
* (soft) init'ed(com/dmdirc/actions/ActionManager.killSwitch)
* (soft) errorInfo != null
* (soft) init'ed(com/dmdirc/ServerManager.me)
* (soft) this.config != null
* (soft) this.listeners != null
* (soft) this.myState.history != null
* ...
*
* Presumptions:
* init'ed(com.dmdirc.logger.ErrorLevel.LOW)
* com.dmdirc.parser.irc.ParserError:getException(...)@1308 != null
* java.util.Arrays:asList(...)@89 init'ed
*
* Postconditions:
* com/dmdirc/ServerManager.me == old com/dmdirc/ServerManager.me
* this.icon == One-of{old this.icon, &"secure-server", &"server", &"server-disconnected"}
* this.myState.state == One-of{old this.myState.state, &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#5)}
* this.myState.state != null
* this.parser == One-of{old this.parser, null}
* this.reconnectTimer == old this.reconnectTimer
* new ArrayList(ServerManager#1) num objects == 0, if init'ed
* new ServerManager(getServerManager#1) num objects == 0, if init'ed
* new ServerManager(getServerManager#1).servers == null
* new Timer(doDelayedReconnect#4) num objects == undefined
* ...
*
* Test Vectors:
* com.dmdirc.parser.irc.ParserError:getException(...)@1305: Inverse{null}, Addr_Set{null}
* java.net.NoRouteToHostException:instanceof(...)@1312: {0}, {1}
* java.net.SocketException:instanceof(...)@1314: {1}, {0}
* java.net.UnknownHostException:instanceof(...)@1310: {0}, {1}
* javax.net.ssl.SSLException:instanceof(...)@1314: {0}, {1}
*/
1287 synchronized (myState) {
1288 if (myState.getState() == ServerState.CLOSING
1289 || myState.getState() == ServerState.DISCONNECTING) {
1290 // Do nothing
1291 return;
1292 } else if (myState.getState() != ServerState.CONNECTING) {
1293 // Shouldn't happen
1294 throw new IllegalStateException("Connect error when not "
1295 + "connecting\n\n" + getStatus().getTransitionHistory());
1296 }
1297
1298 myState.transition(ServerState.TRANSIENTLY_DISCONNECTED);
1299 parser = null;
1300
1301 updateIcon();
1302
1303 String description;
1304
1305 if (errorInfo.getException() == null) {
1306 description = errorInfo.getData();
1307 } else {
1308 final Exception exception = errorInfo.getException();
1309
1310 if (exception instanceof java.net.UnknownHostException) {
1311 description = "Unknown host (unable to resolve)";
1312 } else if (exception instanceof java.net.NoRouteToHostException) {
1313 description = "No route to host";
1314 } else if (exception instanceof java.net.SocketException
1315 || exception instanceof javax.net.ssl.SSLException) {
1316 description = exception.getMessage();
1317 } else {
1318 Logger.appError(ErrorLevel.LOW, "Unknown socket error", exception);
1319 description = "Unknown error: " + exception.getMessage();
1320 }
1321 }
1322
1323 ActionManager.processEvent(CoreActionType.SERVER_CONNECTERROR, null,
1324 this, description);
1325
1326 handleNotification("connectError", getName(), description);
1327
1328 if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
1329 "reconnectonconnectfailure")) {
1330 doDelayedReconnect();
1331 }
1332 }
1333 }
1334
1335 /**
1336 * Called when we fail to receive a ping reply within a set period of time.
1337 */
1338 public void onPingFailed() {
/*
P/P * Method: void onPingFailed()
*
* Preconditions:
* com/dmdirc/Main.controller != null
* init'ed(com/dmdirc/actions/ActionManager.killSwitch)
* this.parser != null
* this.serverInfo != null
* this.config != null
* (soft) init'ed(com.dmdirc.Server$4__static_init.new int[](Server$4__static_init#1)[...])
* (soft) com.dmdirc.actions.CoreActionType__static_init.new CoreActionType(CoreActionType__static_init#33).type != null
* (soft) init'ed(com/dmdirc/ServerManager.me)
* (soft) this.myState.state != null
* (soft) this.parserThread != null
* ...
*
* Presumptions:
* (int) (java.lang.Math:floor(...)@1339) in {-231..232-1}
* com.dmdirc.ui.interfaces.UIController:getStatusBar(...)@1339 != null
*
* Postconditions:
* com/dmdirc/ServerManager.me == old com/dmdirc/ServerManager.me
* this.awayMessage == old this.awayMessage
* this.icon == One-of{old this.icon, &"secure-server", &"server", &"server-disconnected"}
* this.myState.state != null
* this.parser == old this.parser
* this.parser != null
* this.parserThread == old this.parserThread
* this.parserThread != null
* this.profile == old this.profile
* init'ed(this.profile)
* ...
*/
1339 Main.getUI().getStatusBar().setMessage("No ping reply from "
1340 + getName() + " for over "
1341 + ((int) (Math.floor(parser.getPingTime(false) / 1000.0)))
1342 + " seconds.", null, 10);
1343
1344 ActionManager.processEvent(CoreActionType.SERVER_NOPING, null, this,
1345 Long.valueOf(parser.getPingTime(false)));
1346
1347 if (parser.getPingTime(false)
1348 >= getConfigManager().getOptionInt(DOMAIN_SERVER, "pingtimeout")) {
1349 handleNotification("stonedServer", getName());
1350 reconnect();
1351 }
1352 }
1353
1354 /**
1355 * Called after the parser receives the 005 headers from the server.
1356 */
1357 @Precondition("State is CONNECTING")
1358 public void onPost005() {
/*
P/P * Method: void onPost005()
*
* Preconditions:
* init'ed(com/dmdirc/actions/ActionManager.killSwitch)
* this.myState.state != null
* this.autochannels != null
* this.config != null
* this.ignoreList != null
* this.listeners != null
* this.myState != null
* this.myState.history != null
* this.myState.state.transitions != null
* this.parser != null
* ...
*
* Presumptions:
* chan.channelInfo@1381 != null
* chan.config@1381 != null
* chan.listeners@1381 != null
* chan.server.parser@1381 != null
* chan.server@1381 != null
* ...
*
* Postconditions:
* com/dmdirc/ServerManager.me == old com/dmdirc/ServerManager.me
* init'ed(this.converter)
* this.icon == One-of{old this.icon, &"secure-server", &"server", &"server-disconnected", &"channel"}
* this.icon in Addr_Set{&"secure-server",&"server",&"channel"}
* this.myState.state == &com.dmdirc.ServerState__static_init.new ServerState(ServerState__static_init#3)
* new ArrayList(ServerManager#1) num objects == 0, if init'ed
* new ServerManager(getServerManager#1) num objects == 0, if init'ed
* new ServerManager(getServerManager#1).servers == null
*
* Test Vectors:
* com.dmdirc.config.ConfigManager:getOptionBool(...)@1380: {0}, {1}
* java.util.Iterator:hasNext(...)@1381: {0}, {1}
* java.util.Iterator:hasNext(...)@1386: {0}, {1}
*/
1359 synchronized (myState) {
1360 if (myState.getState() != ServerState.CONNECTING) {
1361 // Shouldn't happen
1362 throw new IllegalStateException("Received onPost005 while not "
1363 + "connecting\n\n" + myState.getTransitionHistory());
1364 }
1365
1366 if (myState.getState() != ServerState.CONNECTING) {
1367 // We've transitioned while waiting for the lock. Just abort.
1368 return;
1369 }
1370
1371 myState.transition(ServerState.CONNECTED);
1372
1373 updateIcon();
1374
1375 getConfigManager().migrate(parser.getIRCD(true), getNetwork(), getName());
1376 updateIgnoreList();
1377
1378 converter = parser.getIRCStringConverter();
1379
1380 if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "rejoinchannels")) {
1381 for (Channel chan : channels.values()) {
1382 chan.join();
1383 }
1384 }
1385
1386 for (String channel : autochannels) {
1387 parser.joinChannel(channel);
1388 }
1389
1390 checkModeAliases();
1391 }
1392
1393 ActionManager.processEvent(CoreActionType.SERVER_CONNECTED, null, this);
1394 }
1395
1396 /**
1397 * Checks that we have the neccessary mode aliases for this server.
1398 */
1399 private void checkModeAliases() {
1400 // Check we have mode aliases
/*
P/P * Method: void checkModeAliases()
*
* Preconditions:
* this.parser != null
* (soft) this.config != null
*
* Presumptions:
* arr$.length@1408 <= 232-1
* arr$.length@1414 <= 232-1
* init'ed(com.dmdirc.logger.ErrorLevel.LOW)
* com.dmdirc.parser.irc.IRCParser:getUserModeString(...)@1403 != null
*
* Test Vectors:
* com.dmdirc.config.ConfigManager:hasOptionString(...)@1409: {1}, {0}
* com.dmdirc.config.ConfigManager:hasOptionString(...)@1415: {1}, {0}
* java.lang.StringBuffer:length(...)@1423: {-231..0}, {1..232-1}
* java.lang.StringBuffer:length(...)@1428: {-231..0}, {1..232-1}
* java.lang.StringBuffer:length(...)@1429: {-231..0}, {1..232-1}
*/
1401 final String modes = parser.getBoolChanModes() + parser.getListChanModes()
1402 + parser.getSetOnlyChanModes() + parser.getSetUnsetChanModes();
1403 final String umodes = parser.getUserModeString();
1404
1405 final StringBuffer missingModes = new StringBuffer();
1406 final StringBuffer missingUmodes = new StringBuffer();
1407
1408 for (char mode : modes.toCharArray()) {
1409 if (!getConfigManager().hasOptionString(DOMAIN_SERVER, "mode" + mode)) {
1410 missingModes.append(mode);
1411 }
1412 }
1413
1414 for (char mode : umodes.toCharArray()) {
1415 if (!getConfigManager().hasOptionString(DOMAIN_SERVER, "umode" + mode)) {
1416 missingUmodes.append(mode);
1417 }
1418 }
1419
1420 if (missingModes.length() + missingUmodes.length() > 0) {
1421 final StringBuffer missing = new StringBuffer("Missing mode aliases: ");
1422
1423 if (missingModes.length() > 0) {
1424 missing.append("channel: +");
1425 missing.append(missingModes);
1426 }
1427
1428 if (missingUmodes.length() > 0) {
1429 if (missingModes.length() > 0) {
1430 missing.append(' ');
1431 }
1432
1433 missing.append("user: +");
1434 missing.append(missingUmodes);
1435 }
1436
1437 Logger.appError(ErrorLevel.LOW, missing.toString() + " ["
1438 + parser.getIRCD(true) + "]",
1439 new Exception(missing.toString() + "\n" // NOPMD
1440 + "Network: " + getNetwork() + "\n"
1441 + "IRCd: " + parser.getIRCD(false)
1442 + " (" + parser.getIRCD(true) + ")\n"
1443 + "Mode alias version: "
1444 + getConfigManager().getOption("identity", "modealiasversion")
1445 + "\n\n"));
1446 }
1447 }
1448
1449 // </editor-fold>
1450
1451 // <editor-fold defaultstate="collapsed" desc="Ignore lists">
1452
1453 /**
1454 * Retrieves this server's ignore list.
1455 *
1456 * @return This server's ignore list
1457 */
1458 public IgnoreList getIgnoreList() {
/*
P/P * Method: IgnoreList getIgnoreList()
*
* Postconditions:
* return_value == this.ignoreList
* init'ed(return_value)
*/
1459 return ignoreList;
1460 }
1461
1462 /**
1463 * Updates this server's ignore list to use the entries stored in the
1464 * config manager.
1465 */
1466 public void updateIgnoreList() {
/*
P/P * Method: void updateIgnoreList()
*
* Preconditions:
* this.config != null
* this.ignoreList != null
*/
1467 ignoreList.clear();
1468 ignoreList.addAll(getConfigManager().getOptionList("network", "ignorelist"));
1469 }
1470
1471 /**
1472 * Saves the contents of our ignore list to the network identity.
1473 */
1474 public void saveIgnoreList() {
/*
P/P * Method: void saveIgnoreList()
*
* Preconditions:
* this.ignoreList != null
* init'ed(this.ignoreList.ignoreInfo)
* this.parser != null
*
* Presumptions:
* com.dmdirc.config.IdentityManager:getNetworkConfig(...)@1497 != null
*/
1475 getNetworkIdentity().setOption("network", "ignorelist", ignoreList.getRegexList());
1476 }
1477
1478 // </editor-fold>
1479
1480 // <editor-fold defaultstate="collapsed" desc="Identity handling">
1481
1482 /**
1483 * Retrieves the identity for this server.
1484 *
1485 * @return This server's identity
1486 */
1487 public Identity getServerIdentity() {
/*
P/P * Method: Identity getServerIdentity()
*
* Preconditions:
* this.serverInfo != null
*
* Postconditions:
* init'ed(return_value)
*/
1488 return IdentityManager.getServerConfig(getName());
1489 }
1490
1491 /**
1492 * Retrieves the identity for this server's network.
1493 *
1494 * @return This server's network identity
1495 */
1496 public Identity getNetworkIdentity() {
/*
P/P * Method: Identity getNetworkIdentity()
*
* Preconditions:
* this.parser != null
*
* Postconditions:
* init'ed(return_value)
*/
1497 return IdentityManager.getNetworkConfig(getNetwork());
1498 }
1499
1500 // </editor-fold>
1501
1502 // <editor-fold defaultstate="collapsed" desc="Invite handling">
1503
1504 /**
1505 * Adds an invite listener to this server.
1506 *
1507 * @param listener The listener to be added
1508 */
1509 public void addInviteListener(final InviteListener listener) {
/*
P/P * Method: void addInviteListener(InviteListener)
*
* Preconditions:
* this.listeners != null
*/
1510 synchronized (listeners) {
1511 listeners.add(InviteListener.class, listener);
1512 }
1513 }
1514
1515 /**
1516 * Removes an invite listener from this server.
1517 *
1518 * @param listener The listener to be removed
1519 */
1520 public void removeInviteListener(final InviteListener listener) {
/*
P/P * Method: void removeInviteListener(InviteListener)
*
* Preconditions:
* this.listeners != null
*/
1521 synchronized (listeners) {
1522 listeners.remove(InviteListener.class, listener);
1523 }
1524 }
1525
1526 /**
1527 * Adds an invite to this server, and fires the appropriate listeners.
1528 *
1529 * @param invite The invite to be added
1530 */
1531 public void addInvite(final Invite invite) {
/*
P/P * Method: void addInvite(Invite)
*
* Preconditions:
* this.invites != null
* this.listeners != null
* (soft) invite != null
*
* Presumptions:
* com.dmdirc.util.ListenerList:get(...)@1542 != null
* java.util.ArrayList:iterator(...)@1533 != null
* java.util.Iterator:next(...)@1533 != null
* java.util.Iterator:next(...)@1542 != null
* oldInvite.channel@1533 != null
*
* Test Vectors:
* java.lang.String:equals(...)@1534: {0}, {1}
* java.util.Iterator:hasNext(...)@1533: {0}, {1}
* java.util.Iterator:hasNext(...)@1542: {1}, {0}
*/
1532 synchronized (invites) {
1533 for (Invite oldInvite : new ArrayList<Invite>(invites)) {
1534 if (oldInvite.getChannel().equals(invite.getChannel())) {
1535 removeInvite(oldInvite);
1536 }
1537 }
1538
1539 invites.add(invite);
1540
1541 synchronized (listeners) {
1542 for (InviteListener listener : listeners.get(InviteListener.class)) {
1543 listener.inviteReceived(this, invite);
1544 }
1545 }
1546 }
1547 }
1548
1549 /**
1550 * Removes all invites for the specified channel.
1551 *
1552 * @param channel The channel to remove invites for
1553 */
1554 public void removeInvites(final String channel) {
/*
P/P * Method: void removeInvites(String)
*
* Preconditions:
* (soft) this.invites != null
* (soft) this.listeners != null
*
* Presumptions:
* invite.channel@1555 != null
* java.util.ArrayList:iterator(...)@1555 != null
* java.util.Iterator:next(...)@1555 != null
*
* Test Vectors:
* java.lang.String:equals(...)@1556: {0}, {1}
* java.util.Iterator:hasNext(...)@1555: {0}, {1}
*/
1555 for (Invite invite : new ArrayList<Invite>(invites)) {
1556 if (invite.getChannel().equals(channel)) {
1557 removeInvite(invite);
1558 }
1559 }
1560 }
1561
1562 /**
1563 * Removes all invites for all channels.
1564 */
1565 private void removeInvites() {
/*
P/P * Method: void removeInvites()
*
* Preconditions:
* (soft) this.invites != null
* (soft) this.listeners != null
*
* Presumptions:
* java.util.ArrayList:iterator(...)@1566 != null
*
* Test Vectors:
* java.util.Iterator:hasNext(...)@1566: {0}, {1}
*/
1566 for (Invite invite : new ArrayList<Invite>(invites)) {
1567 removeInvite(invite);
1568 }
1569 }
1570
1571 /**
1572 * Removes an invite from this server, and fires the appropriate listeners.
1573 *
1574 * @param invite The invite to be removed
1575 */
1576 public void removeInvite(final Invite invite) {
/*
P/P * Method: void removeInvite(Invite)
*
* Preconditions:
* this.invites != null
* this.listeners != null
*
* Presumptions:
* com.dmdirc.util.ListenerList:get(...)@1581 != null
* java.util.Iterator:next(...)@1581 != null
*
* Test Vectors:
* java.util.Iterator:hasNext(...)@1581: {1}, {0}
*/
1577 synchronized (invites) {
1578 invites.remove(invite);
1579
1580 synchronized (listeners) {
1581 for (InviteListener listener : listeners.get(InviteListener.class)) {
1582 listener.inviteExpired(this, invite);
1583 }
1584 }
1585 }
1586 }
1587
1588 /**
1589 * Retusnt the list of invites for this server.
1590 *
1591 * @return Invite list
1592 */
1593 public List<Invite> getInvites() {
/*
P/P * Method: List getInvites()
*
* Postconditions:
* return_value == this.invites
* init'ed(return_value)
*/
1594 return invites;
1595 }
1596
1597 // </editor-fold>
1598
1599 // <editor-fold defaultstate="collapsed" desc="Away state handling">
1600
1601 /**
1602 * Adds an away state lisener to this server.
1603 *
1604 * @param listener The listener to be added
1605 */
1606 public void addAwayStateListener(final AwayStateListener listener) {
/*
P/P * Method: void addAwayStateListener(AwayStateListener)
*
* Preconditions:
* this.listeners != null
*/
1607 synchronized (listeners) {
1608 listeners.add(AwayStateListener.class, listener);
1609 }
1610 }
1611
1612 /**
1613 * Removes an away state lisener from this server.
1614 *
1615 * @param listener The listener to be removed
1616 */
1617 public void removeAwayStateListener(final AwayStateListener listener) {
/*
P/P * Method: void removeAwayStateListener(AwayStateListener)
*
* Preconditions:
* this.listeners != null
*/
1618 synchronized (listeners) {
1619 listeners.remove(AwayStateListener.class, listener);
1620 }
1621 }
1622
1623 /**
1624 * Updates our away state and fires the relevant listeners.
1625 *
1626 * @param message The away message to use, or null if we're not away.
1627 */
1628 public void updateAwayState(final String message) {
/*
P/P * Method: void updateAwayState(String)
*
* Preconditions:
* init'ed(this.awayMessage)
* (soft) this.listeners != null
*
* Presumptions:
* com.dmdirc.util.ListenerList:get(...)@1638 != null
* com.dmdirc.util.ListenerList:get(...)@1642 != null
* java.util.Iterator:next(...)@1638 != null
* java.util.Iterator:next(...)@1642 != null
*
* Postconditions:
* this.awayMessage == One-of{old this.awayMessage, message}
* init'ed(this.awayMessage)
*
* Test Vectors:
* message: Inverse{null}, Addr_Set{null}
* this.awayMessage: Addr_Set{null}, Inverse{null}
* java.lang.String:equals(...)@1629: {1}, {0}
*/
1629 if ((awayMessage != null && awayMessage.equals(message))
1630 || (awayMessage == null && message == null)) {
1631 return;
1632 }
1633
1634 awayMessage = message;
1635
1636 synchronized (listeners) {
1637 if (message == null) {
1638 for (AwayStateListener listener : listeners.get(AwayStateListener.class)) {
1639 listener.onBack();
1640 }
1641 } else {
1642 for (AwayStateListener listener : listeners.get(AwayStateListener.class)) {
1643 listener.onAway(message);
1644 }
1645 }
1646 }
1647 }
1648
1649 // </editor-fold>
1650
1651 }
SofCheck Inspector Build Version : 2.17854
| Server.java |
2009-Jun-25 01:54:24 |
| Server.class |
2009-Sep-02 17:04:17 |
| Server$1.class |
2009-Sep-02 17:04:17 |
| Server$2.class |
2009-Sep-02 17:04:17 |
| Server$3.class |
2009-Sep-02 17:04:17 |
| Server$4.class |
2009-Sep-02 17:04:17 |