ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/i-scream/projects/cms/source/server/uk/org/iscream/cms/server/client/alerters/IRC__Alerter.java
(Generate patch)

Comparing projects/cms/source/server/uk/org/iscream/cms/server/client/alerters/IRC__Alerter.java (file contents):
Revision 1.14 by ajm, Mon Mar 5 12:09:28 2001 UTC vs.
Revision 1.26 by tdb, Sat Mar 24 03:47:50 2001 UTC

# Line 1 | Line 1
1   //---PACKAGE DECLARATION---
2 < package uk.ac.ukc.iscream.client.alerters;
2 > package uk.org.iscream.client.alerters;
3  
4   //---IMPORTS---
5 < import uk.ac.ukc.iscream.client.*;
6 < import uk.ac.ukc.iscream.core.*;
7 < import uk.ac.ukc.iscream.util.*;
8 < import uk.ac.ukc.iscream.componentmanager.*;
9 <
5 > import uk.org.iscream.client.*;
6 > import uk.org.iscream.core.*;
7 > import uk.org.iscream.util.*;
8 > import uk.org.iscream.componentmanager.*;
9   import java.io.*;
10   import java.net.*;
11   import java.util.*;
12 < import java.text.*;
12 > import java.text.DateFormat;
13  
14   /**
15   * This Alert sends an IRC message.
# Line 21 | Line 20 | import java.text.*;
20   * @author  $Author$
21   * @version $Id$
22   */
23 < public class IRC__Alerter implements PluginAlerter {
23 > public class IRC__Alerter extends AlerterSkeleton {
24  
25   //---FINAL ATTRIBUTES---
26  
# Line 45 | Line 44 | public class IRC__Alerter implements PluginAlerter {
44   //---CONSTRUCTORS---
45  
46      public IRC__Alerter() {
47 <                        
47 >        super();
48          // connect to the IRC server
49          _ircbot = new IRCBot();
50 +        // set it's name and start it
51 +        _ircbot.setName("client.IRC__Alerter$IRCBot");
52          _ircbot.start();
53          _startTime = System.currentTimeMillis();
53        
54          _logger.write(toString(), Logger.SYSINIT, "IRC Alerter started");
55      }
56  
57   //---PUBLIC METHODS---
58  
59 +    /**
60 +     * Implements the abstract method from the skeleton class.
61 +     * This method will attempt to send an alert
62 +     * message over the IRC channel.
63 +     *
64 +     * @param alert the alert to send
65 +     */
66      public void sendAlert(Alert alert) {
67          // only send alerts if we're active
68          if(_active) {
69 <            ConfigurationProxy cp = ConfigurationProxy.getInstance();
70 <            String levelName = cp.getProperty(_name, "Alerter.IRC.level");
71 <            int level = StringUtils.getStringPos(levelName, Alert.alertLevels);
72 <            // only send if it's equal (or above) our level
73 <            if((alert.getLevel() == 0) || (alert.getLevel() >= level)) {
74 <                String alertType = Alert.alertLevels[alert.getLevel()];
75 <                String thresholdType = Alert.thresholdLevels[alert.getThreshold()];
76 <                // sort out the message
70 <                String message = cp.getProperty(_name, "Alerter.IRC.message");
71 <                message = StringUtils.replaceText(message, "%level%", alertType);
72 <                message = StringUtils.replaceText(message, "%threshold%", thresholdType);
73 <                message = StringUtils.replaceText(message, "%source%", alert.getSource());
74 <                message = StringUtils.replaceText(message, "%value%", alert.getValue());
75 <                message = StringUtils.replaceText(message, "%thresholdValue%", alert.getThresholdValue());
76 <                message = StringUtils.replaceText(message, "%attributeName%", alert.getAttributeName());
77 <                message = StringUtils.replaceText(message, "%timeTillNextAlert%",  getTimeString(Long.parseLong(alert.getTimeTillNextAlert())));
78 <                
79 <                // send the message
80 <                _logger.write(toString(), Logger.DEBUG, "Sending " + _name + " at "+ alertType + " level");
81 <                _ircbot.sendMsg(message);
82 <                _lastAlert = message;
83 <                _lastAlertTime = System.currentTimeMillis();
84 <                _alertCount ++;
69 >            // sort out the message      
70 >            String alertType = Alert.alertLevels[alert.getLevel()];        
71 >            String message;
72 >            try {
73 >                message = _cp.getProperty(_name, "Alerter.IRC.message");
74 >            } catch (PropertyNotFoundException e) {
75 >                message = NOT_CONFIGURED;
76 >                _logger.write(toString(), Logger.WARNING, "Alerter.IRC.message value unavailable using default of " + message);
77              }
78 +            message = processAlertMessage(message, alert);                
79 +            
80 +            // send the message
81 +            _logger.write(toString(), Logger.DEBUG, "Sending " + _name + " at "+ alertType + " level");
82 +            _ircbot.sendMsg(message);
83 +            // count sent alerts
84 +            _alertCount++;
85 +        } else {
86 +            // don't send, but keep a count that we ignored it
87 +            _ignoredCount++;
88          }
89 +        // we'll always store the "last alert", regardless
90 +        // of whether we actually display it or not
91 +        _lastAlert = message;
92 +        _lastAlertTime = System.currentTimeMillis();
93      }
94  
95      /**
96       * Overrides the {@link java.lang.Object#toString() Object.toString()}
97       * method to provide clean logging (every class should have this).
98       *
99 <     * This uses the uk.ac.ukc.iscream.util.NameFormat class
99 >     * This uses the uk.org.iscream.util.NameFormat class
100       * to format the toString()
101       *
102       * @return the name of this class and its CVS revision
# Line 102 | Line 108 | public class IRC__Alerter implements PluginAlerter {
108              REVISION);
109      }
110  
111 <    /**
112 <     * return the String representation of what the filter does
111 >    /**
112 >     * Return the String representation of what the alerter does
113 >     *
114 >     * @return the description
115       */
116      public String getDescription(){
117          return DESC;
# Line 111 | Line 119 | public class IRC__Alerter implements PluginAlerter {
119  
120   //---PRIVATE METHODS---
121  
114    private String getTimeString(long time) {
115        String timeString = null;
116        if (time >= 60) {
117            timeString = (time / 60) + " minute(s)";
118        } else if (time >= 3600) {
119            timeString = ((time/60) / 60) + " hour(s)";
120        } else {
121            timeString = time + " second(s)";
122        }
123        return timeString;
124    }
125
122   //---ACCESSOR/MUTATOR METHODS---
123  
124   //---ATTRIBUTES---
# Line 150 | Line 146 | public class IRC__Alerter implements PluginAlerter {
146      /**
147       * Number of alerts sent
148       */
149 <    private long _alertCount = 0;
149 >    private int _alertCount = 0;
150      
151      /**
152 +     * Number of alerts ignored when in "stopped" mode
153 +     */
154 +    private int _ignoredCount = 0;
155 +    
156 +    /**
157       * Time of IRCBot startup
158       */
159      private long _startTime;
# Line 166 | Line 167 | public class IRC__Alerter implements PluginAlerter {
167       * can be placed here.  This name could also
168       * be changed to null for utility classes.
169       */
170 <    private String _name = "IRC Alert";
170 >    private String _name = "IRC";
171  
171    /**
172     * This holds a reference to the
173     * system logger that is being used.
174     */
175    private Logger _logger = ReferenceManager.getInstance().getLogger();
176
172   //---STATIC ATTRIBUTES---
173  
174   //---INNER CLASSES---
# Line 186 | Line 181 | public class IRC__Alerter implements PluginAlerter {
181       */
182      class IRCBot extends Thread {
183          
184 +        public static final String DEFAULT_STARTUP_NOTICE = "i-scream ircbot starting...";
185 +        
186          /**
187           * Main thread loop, this part of the class listens for
188           * messages from the server, and acts accordingly. At the
# Line 197 | Line 194 | public class IRC__Alerter implements PluginAlerter {
194              while(run) {
195                  // flag so we can stop the loop
196                  boolean doRead = true;
197 +                // get the startup notice
198 +                String startupNotice;
199 +                try {
200 +                    startupNotice = ConfigurationProxy.getInstance().getProperty(_name, "Alerter.IRC.startupNotice");
201 +                } catch (PropertyNotFoundException e) {
202 +                    startupNotice = DEFAULT_STARTUP_NOTICE;
203 +                    _logger.write(this.toString(), Logger.WARNING, "Configuration error: "+e);
204 +                }
205                  // connect to the IRC server
206                  try {
207                      connect();
208 <                    sendNotice(ConfigurationProxy.getInstance().getProperty(_name, "Alerter.IRC.startupNotice"));
208 >                    sendNotice(startupNotice);
209                  } catch(IOException e) {
210                      doRead=false;
211                      _logger.write(this.toString(), Logger.ERROR, "Error connecting: "+e);
# Line 236 | Line 241 | public class IRC__Alerter implements PluginAlerter {
241                  } catch (NumberFormatException e) {
242                      delayTime = DEFAULT_RECONNECT_DELAY;
243                      _logger.write(this.toString(), Logger.WARNING, "Erronous Alerter.IRC.reconnectDelay value in configuration using default of " + delayTime + " seconds");
244 <                } catch (org.omg.CORBA.MARSHAL e2) {
244 >                } catch (PropertyNotFoundException e) {
245                      delayTime = DEFAULT_RECONNECT_DELAY;
246                      _logger.write(this.toString(), Logger.WARNING, "Alerter.IRC.reconnectDelay value unavailable using default of " + delayTime + " seconds");
247                  }
# Line 306 | Line 311 | public class IRC__Alerter implements PluginAlerter {
311          public void connect() throws IOException {
312              ConfigurationProxy cp = ConfigurationProxy.getInstance();
313              // setup the socket, reader and writer
314 <            String server = cp.getProperty(_name, "Alerter.IRC.IRCServer");
315 <            int port = Integer.parseInt(cp.getProperty(_name, "Alerter.IRC.IRCPort"));
314 >            String server;
315 >            int port;
316 >            try {
317 >                server = cp.getProperty(_name, "Alerter.IRC.IRCServer");
318 >                port = Integer.parseInt(cp.getProperty(_name, "Alerter.IRC.IRCPort"));
319 >            } catch (PropertyNotFoundException e) {
320 >                _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
321 >                throw new IOException("Can't get irc server details due to configuration error");
322 >            } catch (NumberFormatException e) {
323 >                _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
324 >                throw new IOException("Can't get irc server details due to malformed configuration");
325 >            }
326              _socket = new Socket(server, port);
327              _socketIn = new BufferedReader(new InputStreamReader(_socket.getInputStream()));
328              _socketOut = new PrintWriter(_socket.getOutputStream(), true);
329              //_socketOut.println("PASS");
330              // send USER details
331 <            String user = cp.getProperty(_name, "Alerter.IRC.user");
332 <            String comment = cp.getProperty(_name, "Alerter.IRC.comment");
331 >            String user, comment;
332 >            try {
333 >                user = cp.getProperty(_name, "Alerter.IRC.user");
334 >                comment = cp.getProperty(_name, "Alerter.IRC.comment");
335 >            } catch (PropertyNotFoundException e) {
336 >                _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
337 >                throw new IOException("Can't get user details due to configuration error");
338 >            }
339              _socketOut.println("USER "+user+" 8 * :"+comment);
340              // attempt to get a nick
341 <            String nickList = cp.getProperty(_name, "Alerter.IRC.nickList");
341 >            String nickList;
342 >            try {
343 >                nickList = cp.getProperty(_name, "Alerter.IRC.nickList");
344 >            } catch (PropertyNotFoundException e) {
345 >                _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
346 >                throw new IOException("Can't get nickname due to configuration error");
347 >            }
348              StringTokenizer st = new StringTokenizer(nickList, ";");
349              boolean ok = false;
350              // try until we exhaust our list
# Line 349 | Line 376 | public class IRC__Alerter implements PluginAlerter {
376                  throw new IOException("All nicknames in use");
377              }
378              // join the channel
379 <            _channel = cp.getProperty(_name, "Alerter.IRC.channel");
379 >            try {
380 >                _channel = cp.getProperty(_name, "Alerter.IRC.channel");
381 >            } catch (PropertyNotFoundException e) {
382 >                _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
383 >                throw new IOException("Can't get channel name due to configuration error");
384 >            }
385              _socketOut.println("JOIN "+_channel);
386              // allow alerts
387              _active = true;
# Line 375 | Line 407 | public class IRC__Alerter implements PluginAlerter {
407           * Overrides the {@link java.lang.Object#toString() Object.toString()}
408           * method to provide clean logging (every class should have this).
409           *
410 <         * This uses the uk.ac.ukc.iscream.util.NameFormat class
410 >         * This uses the uk.org.iscream.util.NameFormat class
411           * to format the toString()
412           *
413           * @return the name of this class and its CVS revision
# Line 400 | Line 432 | public class IRC__Alerter implements PluginAlerter {
432                  _socketOut.println("PONG" + line.substring(4));
433              }
434              // see if it's for us
435 <            else if(getMsg(line).startsWith(_nickname+",")
436 <                    || getMsg(line).startsWith(_nickname+":")
437 <                    || getMsg(line).startsWith(_nickname+" ")) {
435 >            else if(getMsg(line).startsWith(_nickname+",") || getMsg(line).startsWith(_nickname+":") || getMsg(line).startsWith(_nickname+" ")) {
436 >                // setup some String's
437 >                String stopCommand, startCommand, timeSinceLastAlertCommand, lastAlertCommand, joinCommand;
438 >                String nickChangeCommand, versionCommand, helpCommand, statCommand, uptimeCommand;
439 >                // get the command set
440 >                try {
441 >                    stopCommand = cp.getProperty(_name, "Alerter.IRC.stopCommand");
442 >                    startCommand = cp.getProperty(_name, "Alerter.IRC.startCommand");
443 >                    timeSinceLastAlertCommand = cp.getProperty(_name, "Alerter.IRC.timeSinceLastAlertCommand");
444 >                    lastAlertCommand = cp.getProperty(_name, "Alerter.IRC.lastAlertCommand");
445 >                    joinCommand = cp.getProperty(_name, "Alerter.IRC.joinCommand");
446 >                    nickChangeCommand = cp.getProperty(_name, "Alerter.IRC.nickChangeCommand");
447 >                    versionCommand = cp.getProperty(_name, "Alerter.IRC.versionCommand");
448 >                    helpCommand = cp.getProperty(_name, "Alerter.IRC.helpCommand");
449 >                    statCommand = cp.getProperty(_name, "Alerter.IRC.statCommand");
450 >                    uptimeCommand = cp.getProperty(_name, "Alerter.IRC.uptimeCommand");
451 >                } catch (PropertyNotFoundException e) {
452 >                    _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
453 >                    // lets bail from handling this line...
454 >                    // ...it's gonna be hard without a command set!
455 >                    return;
456 >                }
457 >                
458                  // we have a message for us
459                  String message = getMsg(line).substring(_nickname.length());
460 <                if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.stopCommand"))!=-1) {
460 >                if(message.indexOf(stopCommand)!=-1) {
461                      _active = false;
462                      sendMsg(getMsgSender(line)+", alerts have been stopped");
463                  }
464 <                else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.startCommand"))!=-1) {
464 >                else if(message.indexOf(startCommand)!=-1) {
465                      _active = true;
466                      sendMsg(getMsgSender(line)+", alerts have been activated");
467                  }
468                  // this needs to go here if it contains the same words as the lastAlertCommand
469 <                else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.timeSinceLastAlertCommand"))!=-1) {
469 >                else if(message.indexOf(timeSinceLastAlertCommand)!=-1) {
470                      if(_lastAlertTime != -1) {
471                          long uptime = (System.currentTimeMillis() - _lastAlertTime) / 1000;
472                          String uptimeText = DateUtils.formatTime(uptime, "%DAYS% days, %HOURS% hours, %MINS% mins, and %SECS% secs");
# Line 424 | Line 476 | public class IRC__Alerter implements PluginAlerter {
476                          sendMsg(getMsgSender(line)+", I've never sent an alert!");
477                      }
478                  }
479 <                else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.lastAlertCommand"))!=-1) {
479 >                else if(message.indexOf(lastAlertCommand)!=-1) {
480                      if(_lastAlertTime != -1) {
481                          String date = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.UK).format(new Date(_lastAlertTime));
482                          sendMsg(getMsgSender(line)+", last alert was at "+date+"; "+_lastAlert);
# Line 434 | Line 486 | public class IRC__Alerter implements PluginAlerter {
486                      }
487                      
488                  }
489 <                else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.joinCommand"))!=-1) {
490 <                    String joinCmd = cp.getProperty(_name, "Alerter.IRC.joinCommand");
489 >                else if(message.indexOf(joinCommand)!=-1) {
490 >                    String joinCmd = joinCommand;
491                      String newChan = message.substring(message.indexOf(joinCmd) + joinCmd.length() + 1);
492                      int endOfChan = newChan.indexOf(" ");
493                      if(endOfChan == -1) {
494                          endOfChan = newChan.length();
495                      }
496                      newChan = newChan.substring(0, endOfChan);
497 <                    sendMsg(getMsgSender(line)+", okay, I'm off to "+newChan);
498 <                    _socketOut.println("PART "+_channel);
499 <                    _socketOut.println("JOIN "+newChan);
500 <                    _channel = newChan;
497 >                    if(newChan.equals(_channel)) {
498 >                        sendMsg(getMsgSender(line)+", I'm already on "+newChan+"!");
499 >                    } else {
500 >                        sendMsg(getMsgSender(line)+", okay, I'm off to "+newChan);
501 >                        _socketOut.println("PART "+_channel);
502 >                        _socketOut.println("JOIN "+newChan);
503 >                        _channel = newChan;
504 >                    }
505                  }
506 <                else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.nickChangeCommand"))!=-1) {
507 <                    String nickChangeCmd = cp.getProperty(_name, "Alerter.IRC.nickChangeCommand");
506 >                else if(message.indexOf(nickChangeCommand)!=-1) {
507 >                    String nickChangeCmd = nickChangeCommand;
508                      String newNick = message.substring(message.indexOf(nickChangeCmd) + nickChangeCmd.length() + 1);
509                      int endOfNick = newNick.indexOf(" ");
510                      if(endOfNick == -1) {
# Line 459 | Line 515 | public class IRC__Alerter implements PluginAlerter {
515                      _socketOut.println("NICK "+newNick);
516                      _nickname = newNick;
517                  }
518 <                else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.versionCommand"))!=-1) {
518 >                else if(message.indexOf(versionCommand)!=-1) {
519                      sendMsg(getMsgSender(line)+", I am version "+REVISION.substring(11, REVISION.length() -2)+" of the i-scream alerting bot");
520                  }
521 <                else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.helpCommand"))!=-1) {
521 >                else if(message.indexOf(helpCommand)!=-1) {
522                      sendPrivMsg(getMsgSender(line), "Hello, I am the i-scream alerting bot version "+REVISION.substring(11, REVISION.length() -2));
523                      sendPrivMsg(getMsgSender(line), "I understand the following commands;");
524 <                    sendPrivMsg(getMsgSender(line), cp.getProperty(_name, "Alerter.IRC.stopCommand"));
525 <                    sendPrivMsg(getMsgSender(line), cp.getProperty(_name, "Alerter.IRC.startCommand"));
526 <                    sendPrivMsg(getMsgSender(line), cp.getProperty(_name, "Alerter.IRC.lastAlertCommand"));
527 <                    sendPrivMsg(getMsgSender(line), cp.getProperty(_name, "Alerter.IRC.joinCommand"));
528 <                    sendPrivMsg(getMsgSender(line), cp.getProperty(_name, "Alerter.IRC.nickChangeCommand"));
529 <                    sendPrivMsg(getMsgSender(line), cp.getProperty(_name, "Alerter.IRC.statCommand"));
530 <                    sendPrivMsg(getMsgSender(line), cp.getProperty(_name, "Alerter.IRC.uptimeCommand"));
531 <                    sendPrivMsg(getMsgSender(line), cp.getProperty(_name, "Alerter.IRC.timeSinceLastAlertCommand"));
532 <                    sendPrivMsg(getMsgSender(line), cp.getProperty(_name, "Alerter.IRC.helpCommand"));
524 >                    sendPrivMsg(getMsgSender(line), stopCommand);
525 >                    sendPrivMsg(getMsgSender(line), startCommand);
526 >                    sendPrivMsg(getMsgSender(line), lastAlertCommand);
527 >                    sendPrivMsg(getMsgSender(line), joinCommand);
528 >                    sendPrivMsg(getMsgSender(line), nickChangeCommand);
529 >                    sendPrivMsg(getMsgSender(line), statCommand);
530 >                    sendPrivMsg(getMsgSender(line), uptimeCommand);
531 >                    sendPrivMsg(getMsgSender(line), timeSinceLastAlertCommand);
532 >                    sendPrivMsg(getMsgSender(line), helpCommand);
533                  }
534 <                else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.statCommand"))!=-1) {
535 <                    sendMsg(getMsgSender(line)+", I have sent a total of "+_alertCount+" alerts!");
534 >                else if(message.indexOf(statCommand)!=-1) {
535 >                    sendMsg(getMsgSender(line)+", I have sent a total of "+_alertCount+" alerts, and ignored a total of "+_ignoredCount+"!");
536                  }
537 <                else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.uptimeCommand"))!=-1) {
537 >                else if(message.indexOf(uptimeCommand)!=-1) {
538                      long uptime = (System.currentTimeMillis() - _startTime) / 1000;
539                      String uptimeText = DateUtils.formatTime(uptime, "%DAYS% days, %HOURS% hours, %MINS% mins, and %SECS% secs");
540                      sendMsg(getMsgSender(line)+", I have been running for "+uptimeText);
541                  }
542 +                else if(message.indexOf("ping")!=-1) {
543 +                    sendMsg("pong");
544 +                }
545                  else if(message.indexOf("do a jibble dance")!=-1) {
546                      // little joke :)
547                      sendAction("jives to the funky beat shouting \"ii--screeeaaammm\"");
548                  }
549                  else {
550 <                    sendMsg(getMsgSender(line)+", "+cp.getProperty(_name, "Alerter.IRC.rejectMessage"));
550 >                    String rejectMessage = NOT_CONFIGURED;
551 >                    try {
552 >                        rejectMessage = cp.getProperty(_name, "Alerter.IRC.rejectMessage");
553 >                    } catch(PropertyNotFoundException e) {
554 >                        _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
555 >                    }
556 >                    sendMsg(getMsgSender(line)+", "+rejectMessage);
557                  }
558              }
559              else if(line.indexOf(_nickname)!=-1 && line.indexOf(_channel)!=-1 && line.indexOf("KICK")!=-1) {
560                  sendPrivMsg(getMsgSender(line), "That wasn't a nice thing to do...");
561 <                _channel = cp.getProperty(_name, "Alerter.IRC.channel");
561 >                try {
562 >                    _channel = cp.getProperty(_name, "Alerter.IRC.channel");
563 >                } catch(PropertyNotFoundException e) {
564 >                    _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
565 >                }
566                  _socketOut.println("JOIN "+_channel);
567              }
568          }
# Line 559 | Line 628 | public class IRC__Alerter implements PluginAlerter {
628           * A reminder of our current nickname...
629           */
630          private String _nickname;
631 +        
632 +        /**
633 +         * This holds a reference to the
634 +         * system logger that is being used.
635 +         */
636 +        protected Logger _logger = ReferenceManager.getInstance().getLogger();
637          
638      }
639  

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines