Repository: sf105/goos-code
Branch: master
Commit: 5dff69366dad
Files: 64
Total size: 72.2 KB
Directory structure:
gitextract_xhlrihb_/
├── .classpath
├── .project
├── build.xml
├── lib/
│ ├── deploy/
│ │ ├── commons-io-1.4.jar
│ │ ├── commons-lang-2.4.jar
│ │ ├── smack_3_1_0.jar
│ │ └── smackx_3_1_0.jar
│ └── develop/
│ ├── cglib-nodep-2.2.jar
│ ├── commons-io-1.4-sources.jar
│ ├── commons-lang-2.4-sources.jar
│ ├── hamcrest-core-1.2.jar
│ ├── hamcrest-core-SNAPSHOT.jar
│ ├── hamcrest-library-1.2.jar
│ ├── hamcrest-library-SNAPSHOT.jar
│ ├── jmock-2.6-SNAPSHOT.jar
│ ├── jmock-junit4-2.6-SNAPSHOT.jar
│ ├── jmock-legacy-2.6-SNAPSHOT.jar
│ ├── junit-4.6-src.jar
│ ├── junit-dep-4.6.jar
│ ├── objenesis-1.0.jar
│ ├── windowlicker-core-DEV.jar
│ └── windowlicker-swing-DEV.jar
├── license.txt
├── src/
│ └── auctionsniper/
│ ├── Auction.java
│ ├── AuctionEventListener.java
│ ├── AuctionHouse.java
│ ├── AuctionSniper.java
│ ├── Main.java
│ ├── SniperCollector.java
│ ├── SniperLauncher.java
│ ├── SniperListener.java
│ ├── SniperPortfolio.java
│ ├── SniperSnapshot.java
│ ├── SniperState.java
│ ├── UserRequestListener.java
│ ├── ui/
│ │ ├── Column.java
│ │ ├── MainWindow.java
│ │ ├── SnipersTableModel.java
│ │ └── SwingThreadSniperListener.java
│ ├── util/
│ │ ├── Announcer.java
│ │ └── Defect.java
│ └── xmpp/
│ ├── AuctionMessageTranslator.java
│ ├── LoggingXMPPFailureReporter.java
│ ├── XMPPAuction.java
│ ├── XMPPAuctionException.java
│ ├── XMPPAuctionHouse.java
│ └── XMPPFailureReporter.java
├── test/
│ ├── end-to-end/
│ │ └── test/
│ │ └── endtoend/
│ │ └── auctionsniper/
│ │ ├── ApplicationRunner.java
│ │ ├── AuctionLogDriver.java
│ │ ├── AuctionSniperDriver.java
│ │ ├── AuctionSniperEndToEndTest.java
│ │ └── FakeAuctionServer.java
│ ├── integration/
│ │ └── test/
│ │ └── integration/
│ │ └── auctionsniper/
│ │ ├── ui/
│ │ │ └── MainWindowTest.java
│ │ └── xmpp/
│ │ └── XMPPAuctionHouseTest.java
│ └── unit/
│ └── test/
│ └── auctionsniper/
│ ├── AuctionSniperTest.java
│ ├── SniperLauncherTest.java
│ ├── SniperPortfolioTest.java
│ ├── SniperSnapshotTest.java
│ ├── SniperStateTests.java
│ ├── ui/
│ │ ├── ColumnTest.java
│ │ └── SnipersTableModelTest.java
│ └── xmpp/
│ ├── AuctionMessageTranslatorTest.java
│ └── LoggingXMPPFailureReporterTest.java
└── tools/
└── openfire-version-is-3.6.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .classpath
================================================
================================================
FILE: .project
================================================
auction-example-06
org.eclipse.jdt.core.javabuilder
org.eclipse.jdt.core.javanature
================================================
FILE: build.xml
================================================
================================================
FILE: license.txt
================================================
This project is Copyright 2009 Stephen Freeman and Nathaniel Pryce
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: src/auctionsniper/Auction.java
================================================
package auctionsniper;
public interface Auction {
void join();
void bid(int amount);
void addAuctionEventListener(AuctionEventListener listener);
}
================================================
FILE: src/auctionsniper/AuctionEventListener.java
================================================
package auctionsniper;
import java.util.EventListener;
public interface AuctionEventListener extends EventListener {
enum PriceSource {
FromSniper, FromOtherBidder;
};
void auctionClosed();
void currentPrice(int price, int increment, PriceSource priceSource);
void auctionFailed();
}
================================================
FILE: src/auctionsniper/AuctionHouse.java
================================================
package auctionsniper;
import auctionsniper.UserRequestListener.Item;
public interface AuctionHouse {
Auction auctionFor(Item item);
}
================================================
FILE: src/auctionsniper/AuctionSniper.java
================================================
package auctionsniper;
import auctionsniper.UserRequestListener.Item;
import auctionsniper.util.Announcer;
public class AuctionSniper implements AuctionEventListener {
private final Announcer listeners = Announcer.to(SniperListener.class);
private final Auction auction;
private SniperSnapshot snapshot;
private final Item item;
public AuctionSniper(Item item, Auction auction) {
this.item = item;
this.auction = auction;
this.snapshot = SniperSnapshot.joining(item.identifier);
}
public void addSniperListener(SniperListener listener) {
listeners.addListener(listener);
}
public void auctionClosed() {
snapshot = snapshot.closed();
notifyChange();
}
public void auctionFailed() {
snapshot = snapshot.failed();
notifyChange();
}
public void currentPrice(int price, int increment, PriceSource priceSource) {
switch(priceSource) {
case FromSniper:
snapshot = snapshot.winning(price);
break;
case FromOtherBidder:
int bid = price + increment;
if (item.allowsBid(bid)) {
auction.bid(bid);
snapshot = snapshot.bidding(price, bid);
} else {
snapshot = snapshot.losing(price);
}
break;
}
notifyChange();
}
public SniperSnapshot getSnapshot() {
return snapshot;
}
private void notifyChange() {
listeners.announce().sniperStateChanged(snapshot);
}
}
================================================
FILE: src/auctionsniper/Main.java
================================================
package auctionsniper;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.SwingUtilities;
import auctionsniper.ui.MainWindow;
import auctionsniper.xmpp.XMPPAuctionHouse;
public class Main {
private static final int ARG_HOSTNAME = 0;
private static final int ARG_USERNAME = 1;
private static final int ARG_PASSWORD = 2;
private final SniperPortfolio portfolio = new SniperPortfolio();
private MainWindow ui;
public Main() throws Exception {
startUserInterface();
}
public static void main(String... args) throws Exception {
Main main = new Main();
XMPPAuctionHouse auctionHouse = XMPPAuctionHouse.connect(args[ARG_HOSTNAME], args[ARG_USERNAME], args[ARG_PASSWORD]);
main.disconnectWhenUICloses(auctionHouse);
main.addUserRequestListenerFor(auctionHouse);
}
private void startUserInterface() throws Exception {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
ui = new MainWindow(portfolio);
}
});
}
private void disconnectWhenUICloses(final XMPPAuctionHouse auctionHouse) {
ui.addWindowListener(new WindowAdapter() {
@Override public void windowClosed(WindowEvent e) {
auctionHouse.disconnect();
}
});
}
private void addUserRequestListenerFor(final AuctionHouse auctionHouse) {
ui.addUserRequestListener(new SniperLauncher(auctionHouse, portfolio));
}
}
================================================
FILE: src/auctionsniper/SniperCollector.java
================================================
package auctionsniper;
public interface SniperCollector {
void addSniper(AuctionSniper sniper);
}
================================================
FILE: src/auctionsniper/SniperLauncher.java
================================================
package auctionsniper;
public class SniperLauncher implements UserRequestListener {
private final AuctionHouse auctionHouse;
private final SniperCollector collector;
public SniperLauncher(AuctionHouse auctionHouse, SniperCollector collector) {
this.auctionHouse = auctionHouse;
this.collector = collector;
}
public void joinAuction(Item item) {
Auction auction = auctionHouse.auctionFor(item);
AuctionSniper sniper = new AuctionSniper(item, auction);
auction.addAuctionEventListener(sniper);
collector.addSniper(sniper);
auction.join();
}
}
================================================
FILE: src/auctionsniper/SniperListener.java
================================================
package auctionsniper;
import java.util.EventListener;
public interface SniperListener extends EventListener {
void sniperStateChanged(SniperSnapshot snapshot);
}
================================================
FILE: src/auctionsniper/SniperPortfolio.java
================================================
package auctionsniper;
import java.util.ArrayList;
import java.util.EventListener;
import auctionsniper.util.Announcer;
public class SniperPortfolio implements SniperCollector {
public interface PortfolioListener extends EventListener {
void sniperAdded(AuctionSniper sniper);
}
private final Announcer announcer = Announcer.to(PortfolioListener.class);
private final ArrayList snipers = new ArrayList();
public void addSniper(AuctionSniper sniper) {
snipers.add(sniper);
announcer.announce().sniperAdded(sniper);
}
public void addPortfolioListener(PortfolioListener listener) {
announcer.addListener(listener);
}
}
================================================
FILE: src/auctionsniper/SniperSnapshot.java
================================================
package auctionsniper;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
public class SniperSnapshot {
public final String itemId;
public final int lastPrice;
public final int lastBid;
public final SniperState state;
public SniperSnapshot(String itemId, int lastPrice, int lastBid, SniperState state) {
this.itemId = itemId;
this.lastPrice = lastPrice;
this.lastBid = lastBid;
this.state = state;
}
@Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
public static SniperSnapshot joining(String itemId) {
return new SniperSnapshot(itemId, 0, 0, SniperState.JOINING);
}
public SniperSnapshot bidding(int newLastPrice, int newLastBid) {
return new SniperSnapshot(itemId, newLastPrice, newLastBid, SniperState.BIDDING);
}
public SniperSnapshot winning(int newLastPrice) {
return new SniperSnapshot(itemId, newLastPrice, lastBid, SniperState.WINNING);
}
public SniperSnapshot losing(int newLastPrice) {
return new SniperSnapshot(itemId, newLastPrice, lastBid, SniperState.LOSING);
}
public SniperSnapshot closed() {
return new SniperSnapshot(itemId, lastPrice, lastBid, state.whenAuctionClosed());
}
public SniperSnapshot failed() {
return new SniperSnapshot(itemId, 0, 0, SniperState.FAILED);
}
public boolean isForSameItemAs(SniperSnapshot sniperSnapshot) {
return itemId.equals(sniperSnapshot.itemId);
}
}
================================================
FILE: src/auctionsniper/SniperState.java
================================================
package auctionsniper;
import auctionsniper.util.Defect;
public enum SniperState {
JOINING {
@Override public SniperState whenAuctionClosed() { return LOST; }
},
BIDDING {
@Override public SniperState whenAuctionClosed() { return LOST; }
},
WINNING {
@Override public SniperState whenAuctionClosed() { return WON; }
},
LOSING {
@Override public SniperState whenAuctionClosed() { return LOST; }
},
LOST,
WON,
FAILED;
public SniperState whenAuctionClosed() {
throw new Defect("Auction is already closed");
}
}
================================================
FILE: src/auctionsniper/UserRequestListener.java
================================================
package auctionsniper;
import java.util.EventListener;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
public interface UserRequestListener extends EventListener {
void joinAuction(Item item);
public static class Item {
public final String identifier;
public final int stopPrice;
public Item(String identifier, int stopPrice) {
this.identifier = identifier;
this.stopPrice = stopPrice;
}
public boolean allowsBid(int bid) {
return bid <= stopPrice;
}
@Override
public boolean equals(Object obj) { return EqualsBuilder.reflectionEquals(this, obj); }
@Override
public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); }
@Override
public String toString() { return "Item: " + identifier + ", stop price: " + stopPrice; }
}
}
================================================
FILE: src/auctionsniper/ui/Column.java
================================================
package auctionsniper.ui;
import auctionsniper.SniperSnapshot;
public enum Column {
ITEM_IDENTIFIER("Item") {
@Override public Object valueIn(SniperSnapshot snapshot) {
return snapshot.itemId;
}
},
LAST_PRICE("Last Price") {
@Override public Object valueIn(SniperSnapshot snapshot) {
return snapshot.lastPrice;
}
},
LAST_BID("Last Bid") {
@Override public Object valueIn(SniperSnapshot snapshot) {
return snapshot.lastBid;
}
},
SNIPER_STATE("State") {
@Override public Object valueIn(SniperSnapshot snapshot) {
return SnipersTableModel.textFor(snapshot.state);
}
};
abstract public Object valueIn(SniperSnapshot snapshot);
public final String name;
private Column(String name) {
this.name = name;
}
public static Column at(int offset) { return values()[offset]; }
}
================================================
FILE: src/auctionsniper/ui/MainWindow.java
================================================
package auctionsniper.ui;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import auctionsniper.UserRequestListener;
import auctionsniper.SniperPortfolio;
import auctionsniper.UserRequestListener.Item;
import auctionsniper.util.Announcer;
public class MainWindow extends JFrame {
public static final String APPLICATION_TITLE = "Auction Sniper";
private static final String SNIPERS_TABLE_NAME = "Snipers Table";
public static final String MAIN_WINDOW_NAME = "Auction Sniper Main";
public static final String NEW_ITEM_ID_NAME = "item id";
public static final String JOIN_BUTTON_NAME = "join button";
public static final String NEW_ITEM_STOP_PRICE_NAME = "stop price";
private final Announcer userRequests = Announcer.to(UserRequestListener.class);
public MainWindow(SniperPortfolio portfolio){
super("Auction Sniper");
setName(MainWindow.MAIN_WINDOW_NAME);
fillContentPane(makeSnipersTable(portfolio), makeControls());
pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public void addUserRequestListener(UserRequestListener userRequestListener) {
userRequests.addListener(userRequestListener);
}
private void fillContentPane(JTable snipersTable, JPanel controls) {
final Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
contentPane.add(controls, BorderLayout.NORTH);
contentPane.add(new JScrollPane(snipersTable), BorderLayout.CENTER);
}
private JPanel makeControls() {
final JTextField itemIdField = itemIdField();
final JFormattedTextField stopPriceField = stopPriceField();
JPanel controls = new JPanel(new FlowLayout());
controls.add(itemIdField);
controls.add(stopPriceField);
JButton joinAuctionButton = new JButton("Join Auction");
joinAuctionButton.setName(JOIN_BUTTON_NAME);
joinAuctionButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
userRequests.announce().joinAuction(new Item(itemId(), stopPrice()));
}
private String itemId() {
return itemIdField.getText();
}
private int stopPrice() {
return ((Number)stopPriceField.getValue()).intValue();
}
});
controls.add(joinAuctionButton);
return controls;
}
private JTextField itemIdField() {
JTextField itemIdField = new JTextField();
itemIdField.setColumns(10);
itemIdField.setName(NEW_ITEM_ID_NAME);
return itemIdField;
}
private JFormattedTextField stopPriceField() {
JFormattedTextField stopPriceField = new JFormattedTextField(NumberFormat.getIntegerInstance());
stopPriceField.setColumns(7);
stopPriceField.setName(NEW_ITEM_STOP_PRICE_NAME);
return stopPriceField;
}
private JTable makeSnipersTable(SniperPortfolio portfolio) {
SnipersTableModel model = new SnipersTableModel();
portfolio.addPortfolioListener(model);
JTable snipersTable = new JTable(model);
snipersTable.setName(SNIPERS_TABLE_NAME);
return snipersTable;
}
}
================================================
FILE: src/auctionsniper/ui/SnipersTableModel.java
================================================
package auctionsniper.ui;
import java.util.ArrayList;
import javax.swing.table.AbstractTableModel;
import auctionsniper.AuctionSniper;
import auctionsniper.SniperListener;
import auctionsniper.SniperSnapshot;
import auctionsniper.SniperState;
import auctionsniper.SniperPortfolio.PortfolioListener;
import auctionsniper.util.Defect;
public class SnipersTableModel extends AbstractTableModel implements SniperListener, PortfolioListener {
private final static String[] STATUS_TEXT = {
"Joining", "Bidding", "Winning", "Losing", "Lost", "Won", "Failed"
};
private ArrayList snapshots = new ArrayList();
public int getColumnCount() {
return Column.values().length;
}
public int getRowCount() {
return snapshots.size();
}
@Override public String getColumnName(int column) {
return Column.at(column).name;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return Column.at(columnIndex).valueIn(snapshots.get(rowIndex));
}
public static String textFor(SniperState state) {
return STATUS_TEXT[state.ordinal()];
}
public void sniperStateChanged(SniperSnapshot newSnapshot) {
for (int i = 0; i < snapshots.size(); i++) {
if (newSnapshot.isForSameItemAs(snapshots.get(i))) {
snapshots.set(i, newSnapshot);
fireTableRowsUpdated(i, i);
return;
}
}
throw new Defect("No existing Sniper state for " + newSnapshot.itemId);
}
public void sniperAdded(AuctionSniper sniper) {
addSniperSnapshot(sniper.getSnapshot());
sniper.addSniperListener(new SwingThreadSniperListener(this));
}
private void addSniperSnapshot(SniperSnapshot newSniper) {
snapshots.add(newSniper);
int row = snapshots.size() - 1;
fireTableRowsInserted(row, row);
}
}
================================================
FILE: src/auctionsniper/ui/SwingThreadSniperListener.java
================================================
/**
*
*/
package auctionsniper.ui;
import javax.swing.SwingUtilities;
import auctionsniper.SniperListener;
import auctionsniper.SniperSnapshot;
public class SwingThreadSniperListener implements SniperListener {
private final SniperListener delegate;
public SwingThreadSniperListener(SniperListener delegate) {
this.delegate = delegate;
}
public void sniperStateChanged(final SniperSnapshot snapshot) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
delegate.sniperStateChanged(snapshot);
}
});
}
}
================================================
FILE: src/auctionsniper/util/Announcer.java
================================================
package auctionsniper.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;
public class Announcer {
private final T proxy;
private final List listeners = new ArrayList();
public Announcer(Class extends T> listenerType) {
proxy = listenerType.cast(Proxy.newProxyInstance(
listenerType.getClassLoader(),
new Class>[]{listenerType},
new InvocationHandler() {
public Object invoke(Object aProxy, Method method, Object[] args) throws Throwable {
announce(method, args);
return null;
}
}));
}
public void addListener(T listener) {
listeners.add(listener);
}
public void removeListener(T listener) {
listeners.remove(listener);
}
public T announce() {
return proxy;
}
private void announce(Method m, Object[] args) {
try {
for (T listener : listeners) {
m.invoke(listener, args);
}
}
catch (IllegalAccessException e) {
throw new IllegalArgumentException("could not invoke listener", e);
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException)cause;
}
else if (cause instanceof Error) {
throw (Error)cause;
}
else {
throw new UnsupportedOperationException("listener threw exception", cause);
}
}
}
public static Announcer to(Class extends T> listenerType) {
return new Announcer(listenerType);
}
}
================================================
FILE: src/auctionsniper/util/Defect.java
================================================
package auctionsniper.util;
public class Defect extends RuntimeException {
public Defect() {
super();
}
public Defect(String message, Throwable cause) {
super(message, cause);
}
public Defect(String message) {
super(message);
}
public Defect(Throwable cause) {
super(cause);
}
}
================================================
FILE: src/auctionsniper/xmpp/AuctionMessageTranslator.java
================================================
package auctionsniper.xmpp;
import static auctionsniper.AuctionEventListener.PriceSource.FromOtherBidder;
import static auctionsniper.AuctionEventListener.PriceSource.FromSniper;
import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.packet.Message;
import auctionsniper.AuctionEventListener;
import auctionsniper.AuctionEventListener.PriceSource;
public class AuctionMessageTranslator implements MessageListener {
private final AuctionEventListener listener;
private final String sniperId;
private final XMPPFailureReporter failureReporter;
public AuctionMessageTranslator(String sniperId, AuctionEventListener listener, XMPPFailureReporter failureReporter) {
this.sniperId = sniperId;
this.listener = listener;
this.failureReporter = failureReporter;
}
public void processMessage(Chat chat, Message message) {
String messageBody = message.getBody();
try {
translate(messageBody);
} catch (Exception parseException) {
failureReporter.cannotTranslateMessage(sniperId, messageBody, parseException);
listener.auctionFailed();
}
}
private void translate(String messageBody) throws Exception {
AuctionEvent event = AuctionEvent.from(messageBody);
String eventType = event.type();
if ("CLOSE".equals(eventType)) {
listener.auctionClosed();
} if ("PRICE".equals(eventType)) {
listener.currentPrice(event.currentPrice(), event.increment(),
event.isFrom(sniperId));
}
}
private static class AuctionEvent {
private final Map fields = new HashMap();
public String type() throws MissingValueException { return get("Event"); }
public int currentPrice() throws Exception { return getInt("CurrentPrice"); }
public int increment() throws Exception { return getInt("Increment"); }
public PriceSource isFrom(String sniperId) throws MissingValueException {
return sniperId.equals(bidder()) ? FromSniper : FromOtherBidder;
}
private String bidder() throws MissingValueException { return get("Bidder"); }
private int getInt(String fieldName) throws Exception {
return Integer.parseInt(get(fieldName));
}
private String get(String fieldName) throws MissingValueException {
final String value = fields.get(fieldName);
if (value == null) {
throw new MissingValueException(fieldName);
}
return value;
}
private void addField(String field) {
String[] pair = field.split(":");
fields.put(pair[0].trim(), pair[1].trim());
}
static AuctionEvent from(String messageBody) {
AuctionEvent event = new AuctionEvent();
for (String field : fieldsIn(messageBody)) {
event.addField(field);
}
return event;
}
static String[] fieldsIn(String messageBody) {
return messageBody.split(";");
}
}
private static class MissingValueException extends Exception {
public MissingValueException(String fieldName) {
super("Missing value for " + fieldName);
}
}
}
================================================
FILE: src/auctionsniper/xmpp/LoggingXMPPFailureReporter.java
================================================
package auctionsniper.xmpp;
import java.util.logging.Logger;
public class LoggingXMPPFailureReporter implements XMPPFailureReporter {
private static final String MESSAGE_FORMAT = "<%s> Could not translate message \"%s\" because \"%s\"";
private final Logger logger;
public LoggingXMPPFailureReporter(Logger logger) {
this.logger = logger;
}
public void cannotTranslateMessage(String auctionId, String failedMessage, Exception exception) {
logger.severe(String.format(MESSAGE_FORMAT, auctionId, failedMessage, exception.toString()));
}
}
================================================
FILE: src/auctionsniper/xmpp/XMPPAuction.java
================================================
package auctionsniper.xmpp;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import auctionsniper.Auction;
import auctionsniper.AuctionEventListener;
import auctionsniper.util.Announcer;
public class XMPPAuction implements Auction {
public static final String JOIN_COMMAND_FORMAT = "SOLVersion: 1.1; Command: JOIN;";
public static final String BID_COMMAND_FORMAT = "SOLVersion: 1.1; Command: BID; Price: %d;";
private final Announcer auctionEventListeners = Announcer.to(AuctionEventListener.class);
private final Chat chat;
private final XMPPFailureReporter failureReporter;
public XMPPAuction(XMPPConnection connection, String auctionJID, XMPPFailureReporter failureReporter) {
this.failureReporter = failureReporter;
AuctionMessageTranslator translator = translatorFor(connection);
this.chat = connection.getChatManager().createChat( auctionJID, translator);
addAuctionEventListener(chatDisconnectorFor(translator));
}
public void bid(int amount) {
sendMessage(String.format(BID_COMMAND_FORMAT, amount));
}
public void join() {
sendMessage(JOIN_COMMAND_FORMAT);
}
public void addAuctionEventListener(AuctionEventListener listener) {
auctionEventListeners.addListener(listener);
}
private AuctionMessageTranslator translatorFor(XMPPConnection connection) {
return new AuctionMessageTranslator(connection.getUser(), auctionEventListeners.announce(), failureReporter);
}
private AuctionEventListener
chatDisconnectorFor(final AuctionMessageTranslator translator) {
return new AuctionEventListener() {
public void auctionFailed() {
chat.removeMessageListener(translator);
}
public void auctionClosed() { }
public void currentPrice(int price, int increment, PriceSource priceSource) { }
};
}
private void sendMessage(final String message) {
try {
chat.sendMessage(message);
} catch (XMPPException e) {
e.printStackTrace();
}
}
}
================================================
FILE: src/auctionsniper/xmpp/XMPPAuctionException.java
================================================
package auctionsniper.xmpp;
public class XMPPAuctionException extends Exception {
public XMPPAuctionException(String message, Exception cause) {
super(message, cause);
}
}
================================================
FILE: src/auctionsniper/xmpp/XMPPAuctionHouse.java
================================================
package auctionsniper.xmpp;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import org.apache.commons.io.FilenameUtils;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import auctionsniper.Auction;
import auctionsniper.AuctionHouse;
import auctionsniper.UserRequestListener.Item;
public class XMPPAuctionHouse implements AuctionHouse {
private static final String LOGGER_NAME = "auction-sniper";
public static final String LOG_FILE_NAME = "auction-sniper.log";
public static final String ITEM_ID_AS_LOGIN = "auction-%s";
public static final String AUCTION_ID_FORMAT = ITEM_ID_AS_LOGIN + "@%s/" + XMPPAuctionHouse.AUCTION_RESOURCE;
public static final String AUCTION_RESOURCE = "Auction";
private final XMPPConnection connection;
private final LoggingXMPPFailureReporter failureReporter;
public XMPPAuctionHouse(XMPPConnection connection) throws XMPPAuctionException {
this.connection = connection;
this.failureReporter = new LoggingXMPPFailureReporter(makeLogger());
}
public Auction auctionFor(Item item) {
return new XMPPAuction(connection, auctionId(item.identifier, connection), failureReporter);
}
public void disconnect() {
connection.disconnect();
}
public static XMPPAuctionHouse connect(String hostname, String username, String password) throws XMPPAuctionException {
XMPPConnection connection = new XMPPConnection(hostname);
try {
connection.connect();
connection.login(username, password, AUCTION_RESOURCE);
return new XMPPAuctionHouse(connection);
} catch (XMPPException xmppe) {
throw new XMPPAuctionException("Could not connect to auction: " + connection, xmppe);
}
}
private static String auctionId(String itemId, XMPPConnection connection) {
return String.format(AUCTION_ID_FORMAT, itemId, connection.getServiceName());
}
private Logger makeLogger() throws XMPPAuctionException {
Logger logger = Logger.getLogger(LOGGER_NAME);
logger.setUseParentHandlers(false);
logger.addHandler(simpleFileHandler());
return logger;
}
private FileHandler simpleFileHandler() throws XMPPAuctionException {
try {
FileHandler handler = new FileHandler(LOG_FILE_NAME);
handler.setFormatter(new SimpleFormatter());
return handler;
} catch (Exception e) {
throw new XMPPAuctionException("Could not create logger FileHandler "
+ FilenameUtils.getFullPath(LOG_FILE_NAME), e);
}
}
}
================================================
FILE: src/auctionsniper/xmpp/XMPPFailureReporter.java
================================================
package auctionsniper.xmpp;
public interface XMPPFailureReporter {
void cannotTranslateMessage(String auctionId, String failedMessage, Exception exception);
}
================================================
FILE: test/end-to-end/test/endtoend/auctionsniper/ApplicationRunner.java
================================================
package test.endtoend.auctionsniper;
import static auctionsniper.ui.SnipersTableModel.textFor;
import static org.hamcrest.Matchers.containsString;
import static test.endtoend.auctionsniper.FakeAuctionServer.XMPP_HOSTNAME;
import java.io.IOException;
import javax.swing.SwingUtilities;
import auctionsniper.Main;
import auctionsniper.SniperState;
import auctionsniper.ui.MainWindow;
public class ApplicationRunner {
public static final String SNIPER_ID = "sniper";
public static final String SNIPER_PASSWORD = "sniper";
public static final String SNIPER_XMPP_ID = SNIPER_ID + "@" + XMPP_HOSTNAME + "/Auction";
private AuctionLogDriver logDriver = new AuctionLogDriver();
private AuctionSniperDriver driver;
public void startBiddingIn(final FakeAuctionServer... auctions) {
startSniper();
for (FakeAuctionServer auction : auctions) {
openBiddingFor(auction, Integer.MAX_VALUE);
}
}
public void startBiddingWithStopPrice(FakeAuctionServer auction, int stopPrice) {
startSniper();
openBiddingFor(auction, stopPrice);
}
public void hasShownSniperHasLostAuction(FakeAuctionServer auction, int lastPrice, int lastBid) {
driver.showsSniperStatus(auction.getItemId(), lastPrice, lastBid, textFor(SniperState.LOST));
}
public void hasShownSniperIsBidding(FakeAuctionServer auction, int lastPrice, int lastBid) {
driver.showsSniperStatus(auction.getItemId(), lastPrice, lastBid, textFor(SniperState.BIDDING));
}
public void hasShownSniperIsWinning(FakeAuctionServer auction, int winningBid) {
driver.showsSniperStatus(auction.getItemId(), winningBid, winningBid, textFor(SniperState.WINNING));
}
public void hasShownSniperIsLosing(FakeAuctionServer auction, int lastPrice, int lastBid) {
driver.showsSniperStatus(auction.getItemId(), lastPrice, lastBid, textFor(SniperState.LOSING));
}
public void hasShownSniperHasWonAuction(FakeAuctionServer auction, int lastPrice) {
driver.showsSniperStatus(auction.getItemId(), lastPrice, lastPrice, textFor(SniperState.WON));
}
public void hasShownSniperHasFailed(FakeAuctionServer auction) {
driver.showsSniperStatus(auction.getItemId(), 0, 0, textFor(SniperState.FAILED));
}
public void reportsInvalidMessage(FakeAuctionServer auction, String brokenMessage) throws IOException {
logDriver.hasEntry(containsString(brokenMessage));
}
public void stop() {
if (driver != null) {
driver.dispose();
}
}
private void startSniper() {
logDriver.clearLog();
Thread thread = new Thread("Test Application") {
@Override public void run() {
try {
Main.main(XMPP_HOSTNAME, SNIPER_ID, SNIPER_PASSWORD);
} catch (Exception e) {
e.printStackTrace();
}
}
};
thread.setDaemon(true);
thread.start();
makeSureAwtIsLoadedBeforeStartingTheDriverOnOSXToStopDeadlock();
driver = new AuctionSniperDriver(1000);
driver.hasTitle(MainWindow.APPLICATION_TITLE);
driver.hasColumnTitles();
}
private void openBiddingFor(FakeAuctionServer auction, int stopPrice) {
final String itemId = auction.getItemId();
driver.startBiddingWithStopPrice(itemId, stopPrice);
driver.showsSniperStatus(itemId, 0, 0, textFor(SniperState.JOINING));
}
private void makeSureAwtIsLoadedBeforeStartingTheDriverOnOSXToStopDeadlock() {
try {
SwingUtilities.invokeAndWait(new Runnable() { public void run() {} });
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
================================================
FILE: test/end-to-end/test/endtoend/auctionsniper/AuctionLogDriver.java
================================================
package test.endtoend.auctionsniper;
import static org.junit.Assert.assertThat;
import java.io.File;
import java.io.IOException;
import java.util.logging.LogManager;
import org.apache.commons.io.FileUtils;
import org.hamcrest.Matcher;
import auctionsniper.xmpp.XMPPAuctionHouse;
public class AuctionLogDriver {
private final File logFile = new File(XMPPAuctionHouse.LOG_FILE_NAME);
public void hasEntry(Matcher matcher) throws IOException {
assertThat(FileUtils.readFileToString(logFile), matcher);
}
public void clearLog() {
logFile.delete();
LogManager.getLogManager().reset();
}
}
================================================
FILE: test/end-to-end/test/endtoend/auctionsniper/AuctionSniperDriver.java
================================================
package test.endtoend.auctionsniper;
import static auctionsniper.ui.MainWindow.NEW_ITEM_ID_NAME;
import static auctionsniper.ui.MainWindow.NEW_ITEM_STOP_PRICE_NAME;
import static com.objogate.wl.swing.matcher.IterableComponentsMatcher.matching;
import static com.objogate.wl.swing.matcher.JLabelTextMatcher.withLabelText;
import static java.lang.String.valueOf;
import javax.swing.JButton;
import javax.swing.JTextField;
import javax.swing.table.JTableHeader;
import auctionsniper.ui.MainWindow;
import com.objogate.wl.swing.AWTEventQueueProber;
import com.objogate.wl.swing.driver.JButtonDriver;
import com.objogate.wl.swing.driver.JFrameDriver;
import com.objogate.wl.swing.driver.JTableDriver;
import com.objogate.wl.swing.driver.JTableHeaderDriver;
import com.objogate.wl.swing.driver.JTextFieldDriver;
import com.objogate.wl.swing.gesture.GesturePerformer;
@SuppressWarnings("unchecked")
public class AuctionSniperDriver extends JFrameDriver {
public AuctionSniperDriver(int timeoutMillis) {
super(new GesturePerformer(),
JFrameDriver.topLevelFrame(
named(MainWindow.MAIN_WINDOW_NAME),
showingOnScreen()),
new AWTEventQueueProber(timeoutMillis, 100));
}
public void hasColumnTitles() {
JTableHeaderDriver headers = new JTableHeaderDriver(this,
JTableHeader.class);
headers.hasHeaders(
matching(withLabelText("Item"), withLabelText("Last Price"),
withLabelText("Last Bid"), withLabelText("State")));
}
public void showsSniperStatus(String itemId, int lastPrice, int lastBid, String statusText) {
JTableDriver table = new JTableDriver(this);
table.hasRow(
matching(withLabelText(itemId), withLabelText(valueOf(lastPrice)),
withLabelText(valueOf(lastBid)), withLabelText(statusText)));
}
public void startBiddingWithStopPrice(String itemId, int stopPrice) {
textField(NEW_ITEM_ID_NAME).replaceAllText(itemId);
textField(NEW_ITEM_STOP_PRICE_NAME).replaceAllText(String.valueOf(stopPrice));
bidButton().click();
}
private JTextFieldDriver textField(String fieldName) {
JTextFieldDriver newItemId =
new JTextFieldDriver(this, JTextField.class, named(fieldName));
newItemId.focusWithMouse();
return newItemId;
}
private JButtonDriver bidButton() {
return new JButtonDriver(this, JButton.class, named(MainWindow.JOIN_BUTTON_NAME));
}
}
================================================
FILE: test/end-to-end/test/endtoend/auctionsniper/AuctionSniperEndToEndTest.java
================================================
package test.endtoend.auctionsniper;
import org.junit.After;
import org.junit.Test;
public class AuctionSniperEndToEndTest {
private final FakeAuctionServer auction = new FakeAuctionServer("item-54321");
private final FakeAuctionServer auction2 = new FakeAuctionServer("item-65432");
private final ApplicationRunner application = new ApplicationRunner();
@Test public void
sniperJoinsAuctionUntilAuctionCloses() throws Exception {
auction.startSellingItem();
application.startBiddingIn(auction);
auction.hasReceivedJoinRequestFrom(ApplicationRunner.SNIPER_XMPP_ID);
auction.announceClosed();
application.hasShownSniperHasLostAuction(auction, 0, 0);
}
@Test public void
sniperMakesAHigherBidButLoses() throws Exception {
auction.startSellingItem();
application.startBiddingIn(auction);
auction.hasReceivedJoinRequestFrom(ApplicationRunner.SNIPER_XMPP_ID);
auction.reportPrice(1000, 98, "other bidder");
application.hasShownSniperIsBidding(auction, 1000, 1098);
auction.hasReceivedBid(1098, ApplicationRunner.SNIPER_XMPP_ID);
auction.announceClosed();
application.hasShownSniperHasLostAuction(auction, 1000, 1098);
}
@Test public void
sniperWinsAnAuctionByBiddingHigher() throws Exception {
auction.startSellingItem();
application.startBiddingIn(auction);
auction.hasReceivedJoinRequestFrom(ApplicationRunner.SNIPER_XMPP_ID);
auction.reportPrice(1000, 98, "other bidder");
application.hasShownSniperIsBidding(auction, 1000, 1098);
auction.hasReceivedBid(1098, ApplicationRunner.SNIPER_XMPP_ID);
auction.reportPrice(1098, 97, ApplicationRunner.SNIPER_XMPP_ID);
application.hasShownSniperIsWinning(auction, 1098);
auction.announceClosed();
application.hasShownSniperHasWonAuction(auction, 1098);
}
@Test public void
sniperBidsForMultipleItems() throws Exception {
auction.startSellingItem();
auction2.startSellingItem();
application.startBiddingIn(auction, auction2);
auction.hasReceivedJoinRequestFrom(ApplicationRunner.SNIPER_XMPP_ID);
auction2.hasReceivedJoinRequestFrom(ApplicationRunner.SNIPER_XMPP_ID);
auction.reportPrice(1000, 98, "other bidder");
auction.hasReceivedBid(1098, ApplicationRunner.SNIPER_XMPP_ID);
auction2.reportPrice(500, 21, "other bidder");
auction2.hasReceivedBid(521, ApplicationRunner.SNIPER_XMPP_ID);
auction.reportPrice(1098, 97, ApplicationRunner.SNIPER_XMPP_ID);
auction2.reportPrice(521, 22, ApplicationRunner.SNIPER_XMPP_ID);
application.hasShownSniperIsWinning(auction, 1098);
application.hasShownSniperIsWinning(auction2, 521);
auction.announceClosed();
auction2.announceClosed();
application.hasShownSniperHasWonAuction(auction, 1098);
application.hasShownSniperHasWonAuction(auction2, 521);
}
@Test public void
sniperLosesAnAuctionWhenThePriceIsTooHigh() throws Exception {
auction.startSellingItem();
application.startBiddingWithStopPrice(auction, 1100);
auction.hasReceivedJoinRequestFrom(ApplicationRunner.SNIPER_XMPP_ID);
auction.reportPrice(1000, 98, "other bidder");
application.hasShownSniperIsBidding(auction, 1000, 1098);
auction.hasReceivedBid(1098, ApplicationRunner.SNIPER_XMPP_ID);
auction.reportPrice(1197, 10, "third party");
application.hasShownSniperIsLosing(auction, 1197, 1098);
auction.reportPrice(1207, 10, "fourth party");
application.hasShownSniperIsLosing(auction, 1207, 1098);
auction.announceClosed();
application.hasShownSniperHasLostAuction(auction, 1207, 1098);
}
@Test public void
sniperReportsInvalidAuctionMessageAndStopsRespondingToEvents()
throws Exception
{
String brokenMessage = "a broken message";
auction.startSellingItem();
auction2.startSellingItem();
application.startBiddingIn(auction, auction2);
auction.hasReceivedJoinRequestFrom(ApplicationRunner.SNIPER_XMPP_ID);
auction.reportPrice(500, 20, "other bidder");
auction.hasReceivedBid(520, ApplicationRunner.SNIPER_XMPP_ID);
auction.sendInvalidMessageContaining(brokenMessage);
application.hasShownSniperHasFailed(auction);
auction.reportPrice(520, 21, "other bidder");
waitForAnotherAuctionEvent();
application.reportsInvalidMessage(auction, brokenMessage);
application.hasShownSniperHasFailed(auction);
}
private void waitForAnotherAuctionEvent() throws Exception {
auction2.hasReceivedJoinRequestFrom(ApplicationRunner.SNIPER_XMPP_ID);
auction2.reportPrice(600, 6, "other bidder");
application.hasShownSniperIsBidding(auction2, 600, 606);
}
@After public void stopAuction() {
auction.stop();
auction2.stop();
}
@After public void stopApplication() {
application.stop();
}
}
================================================
FILE: test/end-to-end/test/endtoend/auctionsniper/FakeAuctionServer.java
================================================
package test.endtoend.auctionsniper;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.hamcrest.Matcher;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import auctionsniper.xmpp.XMPPAuction;
public class FakeAuctionServer {
public static final String ITEM_ID_AS_LOGIN = "auction-%s";
public static final String AUCTION_RESOURCE = "Auction";
public static final String XMPP_HOSTNAME = "localhost";
private static final String AUCTION_PASSWORD = "auction";
private final SingleMessageListener messageListener = new SingleMessageListener();
private final String itemId;
private final XMPPConnection connection;
private Chat currentChat;
public FakeAuctionServer(String itemId) {
this.itemId = itemId;
this.connection = new XMPPConnection(XMPP_HOSTNAME);
}
public void startSellingItem() throws XMPPException {
connection.connect();
connection.login(String.format(ITEM_ID_AS_LOGIN, itemId), AUCTION_PASSWORD,
AUCTION_RESOURCE);
connection.getChatManager().addChatListener(new ChatManagerListener() {
public void chatCreated(Chat chat, boolean createdLocally) {
currentChat = chat;
chat.addMessageListener(messageListener);
}
});
}
public void sendInvalidMessageContaining(String brokenMessage) throws XMPPException {
currentChat.sendMessage(brokenMessage);
}
public void reportPrice(int price, int increment, String bidder) throws XMPPException {
currentChat.sendMessage(
String.format("SOLVersion: 1.1; Event: PRICE; "
+ "CurrentPrice: %d; Increment: %d; Bidder: %s;",
price, increment, bidder));
}
public void hasReceivedJoinRequestFrom(String sniperId) throws InterruptedException {
receivesAMessageMatching(sniperId, equalTo(XMPPAuction.JOIN_COMMAND_FORMAT));
}
public void hasReceivedBid(int bid, String sniperId) throws InterruptedException {
receivesAMessageMatching(sniperId,
equalTo(String.format(XMPPAuction.BID_COMMAND_FORMAT, bid)));
}
private void receivesAMessageMatching(String sniperId, Matcher super String> messageMatcher) throws InterruptedException {
messageListener.receivesAMessage(messageMatcher);
assertThat(currentChat.getParticipant(), equalTo(sniperId));
}
public void announceClosed() throws XMPPException {
currentChat.sendMessage("SOLVersion: 1.1; Event: CLOSE;");
}
public void stop() {
connection.disconnect();
}
public String getItemId() {
return itemId;
}
public class SingleMessageListener implements MessageListener {
private final ArrayBlockingQueue messages =
new ArrayBlockingQueue(1);
public void processMessage(Chat chat, Message message) {
messages.add(message);
}
public void receivesAMessage() throws InterruptedException {
assertThat("Message", messages.poll(5, TimeUnit.SECONDS), is(notNullValue()));
}
public void receivesAMessage(Matcher super String> messageMatcher)
throws InterruptedException
{
final Message message = messages.poll(5, TimeUnit.SECONDS);
assertThat(message, hasProperty("body", messageMatcher));
}
}
}
================================================
FILE: test/integration/test/integration/auctionsniper/ui/MainWindowTest.java
================================================
package test.integration.auctionsniper.ui;
import static org.hamcrest.Matchers.equalTo;
import org.junit.Test;
import test.endtoend.auctionsniper.AuctionSniperDriver;
import auctionsniper.UserRequestListener;
import auctionsniper.SniperPortfolio;
import auctionsniper.UserRequestListener.Item;
import auctionsniper.ui.MainWindow;
import com.objogate.wl.swing.probe.ValueMatcherProbe;
public class MainWindowTest {
private final MainWindow mainWindow = new MainWindow(new SniperPortfolio());
private final AuctionSniperDriver driver = new AuctionSniperDriver(100);
@Test public void
makesUserRequestWhenJoinButtonClicked() {
final ValueMatcherProbe- itemProbe =
new ValueMatcherProbe
- (equalTo(new Item("an item-id", 789)),
"item request");
mainWindow.addUserRequestListener(
new UserRequestListener() {
public void joinAuction(Item item) {
itemProbe.setReceivedValue(item);
}
});
driver.startBiddingWithStopPrice("an item-id", 789);
driver.check(itemProbe);
}
}
================================================
FILE: test/integration/test/integration/auctionsniper/xmpp/XMPPAuctionHouseTest.java
================================================
package test.integration.auctionsniper.xmpp;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.CountDownLatch;
import org.jivesoftware.smack.XMPPException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import test.endtoend.auctionsniper.ApplicationRunner;
import test.endtoend.auctionsniper.FakeAuctionServer;
import auctionsniper.Auction;
import auctionsniper.AuctionEventListener;
import auctionsniper.UserRequestListener.Item;
import auctionsniper.xmpp.XMPPAuctionException;
import auctionsniper.xmpp.XMPPAuctionHouse;
public class XMPPAuctionHouseTest {
private final FakeAuctionServer auctionServer = new FakeAuctionServer("item-54321");
private XMPPAuctionHouse auctionHouse;
@Before public void openConnection() throws XMPPAuctionException {
auctionHouse = XMPPAuctionHouse.connect(FakeAuctionServer.XMPP_HOSTNAME, ApplicationRunner.SNIPER_ID, ApplicationRunner.SNIPER_PASSWORD);
}
@After public void closeConnection() {
if (auctionHouse != null) {
auctionHouse.disconnect();
}
}
@Before public void startAuction() throws XMPPException {
auctionServer.startSellingItem();
}
@After public void stopAuction() {
auctionServer.stop();
}
@Test public void
receivesEventsFromAuctionServerAfterJoining() throws Exception {
CountDownLatch auctionWasClosed = new CountDownLatch(1);
Auction auction = auctionHouse.auctionFor(new Item(auctionServer.getItemId(), 567));
auction.addAuctionEventListener(auctionClosedListener(auctionWasClosed));
auction.join();
auctionServer.hasReceivedJoinRequestFrom(ApplicationRunner.SNIPER_XMPP_ID);
auctionServer.announceClosed();
assertTrue("should have been closed", auctionWasClosed.await(4, SECONDS));
}
private AuctionEventListener
auctionClosedListener(final CountDownLatch auctionWasClosed) {
return new AuctionEventListener() {
public void auctionClosed() {
auctionWasClosed.countDown();
}
public void currentPrice(int price, int increment, PriceSource priceSource) { }
public void auctionFailed() { }
};
}
}
================================================
FILE: test/unit/test/auctionsniper/AuctionSniperTest.java
================================================
package test.auctionsniper;
import static auctionsniper.SniperState.BIDDING;
import static auctionsniper.SniperState.LOSING;
import static auctionsniper.SniperState.LOST;
import static auctionsniper.SniperState.WINNING;
import static auctionsniper.SniperState.WON;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.samePropertyValuesAs;
import static org.junit.Assert.assertThat;
import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.Sequence;
import org.jmock.States;
import org.jmock.integration.junit4.JMock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import auctionsniper.Auction;
import auctionsniper.AuctionSniper;
import auctionsniper.SniperListener;
import auctionsniper.SniperSnapshot;
import auctionsniper.SniperState;
import auctionsniper.AuctionEventListener.PriceSource;
import auctionsniper.UserRequestListener.Item;
@RunWith(JMock.class)
public class AuctionSniperTest {
protected static final String ITEM_ID = "item-id";
public static final Item ITEM = new Item(ITEM_ID, 1234);
private final Mockery context = new Mockery();
private final States sniperState = context.states("sniper");
private final Auction auction = context.mock(Auction.class);
private final SniperListener sniperListener = context.mock(SniperListener.class);
private final AuctionSniper sniper = new AuctionSniper(ITEM, auction);
@Before public void attachListener() {
sniper.addSniperListener(sniperListener);
}
@Test public void
hasInitialStateOfJoining() {
assertThat(sniper.getSnapshot(), samePropertyValuesAs(SniperSnapshot.joining(ITEM_ID)));
}
@Test public void
reportsLostWhenAuctionClosesImmediately() {
context.checking(new Expectations() {{
atLeast(1).of(sniperListener).sniperStateChanged(new SniperSnapshot(ITEM_ID, 0, 0, LOST));
}});
sniper.auctionClosed();
}
@Test public void
bidsHigherAndReportsBiddingWhenNewPriceArrives() {
final int price = 1001;
final int increment = 25;
final int bid = price + increment;
context.checking(new Expectations() {{
one(auction).bid(bid);
atLeast(1).of(sniperListener).sniperStateChanged(new SniperSnapshot(ITEM_ID, price, bid, BIDDING));
}});
sniper.currentPrice(price, increment, PriceSource.FromOtherBidder);
}
@Test public void
doesNotBidAndReportsLosingIfFirstPriceIsAboveStopPrice() {
final int price = 1233;
final int increment = 25;
context.checking(new Expectations() {{
atLeast(1).of(sniperListener).sniperStateChanged(new SniperSnapshot(ITEM_ID, price, 0, LOSING));
}});
sniper.currentPrice(price, increment, PriceSource.FromOtherBidder);
}
@Test public void
doesNotBidAndReportsLosingIfSubsequentPriceIsAboveStopPrice() {
allowingSniperBidding();
context.checking(new Expectations() {{
int bid = 123 + 45;
allowing(auction).bid(bid);
atLeast(1).of(sniperListener).sniperStateChanged(new SniperSnapshot(ITEM_ID, 2345, bid, LOSING)); when(sniperState.is("bidding"));
}});
sniper.currentPrice(123, 45, PriceSource.FromOtherBidder);
sniper.currentPrice(2345, 25, PriceSource.FromOtherBidder);
}
@Test public void
doesNotBidAndReportsLosingIfPriceAfterWinningIsAboveStopPrice() {
final int price = 1233;
final int increment = 25;
allowingSniperBidding();
allowingSniperWinning();
context.checking(new Expectations() {{
int bid = 123 + 45;
allowing(auction).bid(bid);
atLeast(1).of(sniperListener).sniperStateChanged(new SniperSnapshot(ITEM_ID, price, bid, LOSING)); when(sniperState.is("winning"));
}});
sniper.currentPrice(123, 45, PriceSource.FromOtherBidder);
sniper.currentPrice(168, 45, PriceSource.FromSniper);
sniper.currentPrice(price, increment, PriceSource.FromOtherBidder);
}
@Test public void
continuesToBeLosingOnceStopPriceHasBeenReached() {
final Sequence states = context.sequence("sniper states");
final int price1 = 1233;
final int price2 = 1258;
context.checking(new Expectations() {{
atLeast(1).of(sniperListener).sniperStateChanged(new SniperSnapshot(ITEM_ID, price1, 0, LOSING)); inSequence(states);
atLeast(1).of(sniperListener).sniperStateChanged(new SniperSnapshot(ITEM_ID, price2, 0, LOSING)); inSequence(states);
}});
sniper.currentPrice(price1, 25, PriceSource.FromOtherBidder);
sniper.currentPrice(price2, 25, PriceSource.FromOtherBidder);
}
@Test public void
reportsLostIfAuctionClosesWhenBidding() {
allowingSniperBidding();
ignoringAuction();
context.checking(new Expectations() {{
atLeast(1).of(sniperListener).sniperStateChanged(new SniperSnapshot(ITEM_ID, 123, 168, LOST));
when(sniperState.is("bidding"));
}});
sniper.currentPrice(123, 45, PriceSource.FromOtherBidder);
sniper.auctionClosed();
}
@Test public void
reportsLostIfAuctionClosesWhenLosing() {
allowingSniperLosing();
context.checking(new Expectations() {{
atLeast(1).of(sniperListener).sniperStateChanged(new SniperSnapshot(ITEM_ID, 1230, 0, LOST));
when(sniperState.is("losing"));
}});
sniper.currentPrice(1230, 456, PriceSource.FromOtherBidder);
sniper.auctionClosed();
}
@Test public void
reportsIsWinningWhenCurrentPriceComesFromSniper() {
allowingSniperBidding();
ignoringAuction();
context.checking(new Expectations() {{
atLeast(1).of(sniperListener).sniperStateChanged( new SniperSnapshot(ITEM_ID, 135, 135, WINNING)); when(sniperState.is("bidding"));
}});
sniper.currentPrice(123, 12, PriceSource.FromOtherBidder);
sniper.currentPrice(135, 45, PriceSource.FromSniper);
}
@Test public void
reportsWonIfAuctionClosesWhenWinning() {
allowingSniperBidding();
allowingSniperWinning();
ignoringAuction();
context.checking(new Expectations() {{
atLeast(1).of(sniperListener).sniperStateChanged( new SniperSnapshot(ITEM_ID, 135, 135, WON)); when(sniperState.is("winning"));
}});
sniper.currentPrice(123, 12, PriceSource.FromOtherBidder);
sniper.currentPrice(135, 45, PriceSource.FromSniper);
sniper.auctionClosed();
}
@Test public void
reportsFailedIfAuctionFailsWhenBidding() {
ignoringAuction();
allowingSniperBidding();
expectSniperToFailWhenItIs("bidding");
sniper.currentPrice(123, 45, PriceSource.FromOtherBidder);
sniper.auctionFailed();
}
@Test public void
reportsFailedIfAuctionFailsImmediately() {
context.checking(new Expectations() {{
atLeast(1).of(sniperListener).sniperStateChanged(SniperSnapshot.joining(ITEM_ID).failed());
}});
sniper.auctionFailed();
}
@Test public void
reportsFailedIfAuctionFailsWhenLosing() {
allowingSniperLosing();
expectSniperToFailWhenItIs("losing");
sniper.currentPrice(1230, 456, PriceSource.FromOtherBidder);
sniper.auctionFailed();
}
@Test public void
reportsFailedIfAuctionFailsWhenWinning() {
ignoringAuction();
allowingSniperBidding();
allowingSniperWinning();
expectSniperToFailWhenItIs("winning");
sniper.currentPrice(123, 12, PriceSource.FromOtherBidder);
sniper.currentPrice(135, 45, PriceSource.FromSniper);
sniper.auctionFailed();
}
private void expectSniperToFailWhenItIs(final String state) {
context.checking(new Expectations() {{
atLeast(1).of(sniperListener).sniperStateChanged(
new SniperSnapshot(ITEM_ID, 00, 0, SniperState.FAILED));
when(sniperState.is(state));
}});
}
private void ignoringAuction() {
context.checking(new Expectations() {{
ignoring(auction);
}});
}
private void allowingSniperBidding() {
allowSniperStateChange(BIDDING, "bidding");
}
private void allowingSniperLosing() {
allowSniperStateChange(LOSING, "losing");
}
private void allowingSniperWinning() {
allowSniperStateChange(WINNING, "winning");
}
private void allowSniperStateChange(final SniperState newState, final String oldState) {
context.checking(new Expectations() {{
allowing(sniperListener).sniperStateChanged(with(aSniperThatIs(newState))); then(sniperState.is(oldState));
}});
}
private Matcher aSniperThatIs(final SniperState state) {
return new FeatureMatcher( equalTo(state), "sniper that is ", "was") {
@Override
protected SniperState featureValueOf(SniperSnapshot actual) {
return actual.state;
}
};
}
}
================================================
FILE: test/unit/test/auctionsniper/SniperLauncherTest.java
================================================
package test.auctionsniper;
import static org.hamcrest.Matchers.equalTo;
import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.States;
import org.jmock.integration.junit4.JMock;
import org.junit.Test;
import org.junit.runner.RunWith;
import auctionsniper.Auction;
import auctionsniper.AuctionHouse;
import auctionsniper.AuctionSniper;
import auctionsniper.SniperCollector;
import auctionsniper.SniperLauncher;
import auctionsniper.UserRequestListener.Item;
@RunWith(JMock.class)
public class SniperLauncherTest {
private final Mockery context = new Mockery();
private final States auctionState = context.states("auction state").startsAs("not joined");
private final Auction auction = context.mock(Auction.class);
private final AuctionHouse auctionHouse = context.mock(AuctionHouse.class);
private final SniperCollector sniperCollector = context.mock(SniperCollector.class);
private final SniperLauncher launcher = new SniperLauncher(auctionHouse, sniperCollector);
@Test public void
addsNewSniperToCollectorAndThenJoinsAuction() {
final Item item = new Item("item 123", 456);
context.checking(new Expectations() {{
allowing(auctionHouse).auctionFor(item); will(returnValue(auction));
oneOf(auction).addAuctionEventListener(with(sniperForItem(item))); when(auctionState.is("not joined"));
oneOf(sniperCollector).addSniper(with(sniperForItem(item))); when(auctionState.is("not joined"));
one(auction).join(); then(auctionState.is("joined"));
}});
launcher.joinAuction(item);
}
protected MatchersniperForItem(Item item) {
return new FeatureMatcher(equalTo(item.identifier), "sniper with item id", "item") {
@Override protected String featureValueOf(AuctionSniper actual) {
return actual.getSnapshot().itemId;
}
};
}
}
================================================
FILE: test/unit/test/auctionsniper/SniperPortfolioTest.java
================================================
package test.auctionsniper;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.junit.Test;
import org.junit.runner.RunWith;
import auctionsniper.AuctionSniper;
import auctionsniper.SniperPortfolio;
import auctionsniper.UserRequestListener.Item;
import auctionsniper.SniperPortfolio.PortfolioListener;
@RunWith(JMock.class)
public class SniperPortfolioTest {
private final Mockery context = new Mockery();
private final PortfolioListener listener = context.mock(PortfolioListener.class);
private final SniperPortfolio portfolio = new SniperPortfolio();
@Test public void
notifiesListenersOfNewSnipers() {
final AuctionSniper sniper = new AuctionSniper(new Item("item id", 123), null);
context.checking(new Expectations() {{
oneOf(listener).sniperAdded(sniper);
}});
portfolio.addPortfolioListener(listener);
portfolio.addSniper(sniper);
}
}
================================================
FILE: test/unit/test/auctionsniper/SniperSnapshotTest.java
================================================
package test.auctionsniper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import auctionsniper.SniperSnapshot;
import auctionsniper.SniperState;
public class SniperSnapshotTest {
@Test public void
transitionsBetweenStates() {
final String itemId = "item id";
SniperSnapshot joining = SniperSnapshot.joining(itemId);
assertEquals(new SniperSnapshot(itemId, 0, 0, SniperState.JOINING),
joining);
SniperSnapshot bidding = joining.bidding(123, 234);
assertEquals(
new SniperSnapshot(itemId, 123, 234, SniperState.BIDDING),
bidding);
assertEquals(new SniperSnapshot(itemId, 456, 234, SniperState.LOSING),
bidding.losing(456));
assertEquals(
new SniperSnapshot(itemId, 456, 234, SniperState.WINNING),
bidding.winning(456));
assertEquals(
new SniperSnapshot(itemId, 123, 234, SniperState.LOST),
bidding.closed());
assertEquals(
new SniperSnapshot(itemId, 678, 234, SniperState.WON),
bidding.winning(678).closed());
}
@Test public void
comparesItemIdentities() {
assertTrue(
SniperSnapshot.joining("item 1").isForSameItemAs(
SniperSnapshot.joining("item 1")));
assertFalse(
SniperSnapshot.joining("item 1").isForSameItemAs(
SniperSnapshot.joining("item 2")));
}
}
================================================
FILE: test/unit/test/auctionsniper/SniperStateTests.java
================================================
package test.auctionsniper;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import auctionsniper.SniperState;
import auctionsniper.util.Defect;
public class SniperStateTests {
@Test public void
isWonWhenAuctionClosesWhileWinning() {
assertEquals(SniperState.LOST, SniperState.JOINING.whenAuctionClosed());
assertEquals(SniperState.LOST, SniperState.BIDDING.whenAuctionClosed());
assertEquals(SniperState.WON, SniperState.WINNING.whenAuctionClosed());
}
@Test(expected=Defect.class) public void
defectIfAuctionClosesWhenWon() {
SniperState.WON.whenAuctionClosed();
}
@Test(expected=Defect.class) public void
defectIfAuctionClosesWhenLost() {
SniperState.LOST.whenAuctionClosed();
}
}
================================================
FILE: test/unit/test/auctionsniper/ui/ColumnTest.java
================================================
package test.auctionsniper.ui;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import auctionsniper.SniperSnapshot;
import auctionsniper.SniperState;
import auctionsniper.ui.Column;
public class ColumnTest {
@Test public void
retrievesValuesFromASniperSnapshot() {
SniperSnapshot snapshot = new SniperSnapshot("item", 123, 34, SniperState.BIDDING);
assertEquals("item", Column.ITEM_IDENTIFIER.valueIn(snapshot));
assertEquals(123, Column.LAST_PRICE.valueIn(snapshot));
assertEquals(34, Column.LAST_BID.valueIn(snapshot));
assertEquals("Bidding", Column.SNIPER_STATE.valueIn(snapshot));
}
}
================================================
FILE: test/unit/test/auctionsniper/ui/SnipersTableModelTest.java
================================================
package test.auctionsniper.ui;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.samePropertyValuesAs;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import org.hamcrest.Matcher;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import auctionsniper.AuctionSniper;
import auctionsniper.SniperSnapshot;
import auctionsniper.SniperState;
import auctionsniper.UserRequestListener.Item;
import auctionsniper.ui.Column;
import auctionsniper.ui.SnipersTableModel;
import auctionsniper.util.Defect;
@RunWith(JMock.class)
public class SnipersTableModelTest {
private static final String ITEM_ID = "item 0";
private final Mockery context = new Mockery();
private TableModelListener listener = context.mock(TableModelListener.class);
private final SnipersTableModel model = new SnipersTableModel();
private final AuctionSniper sniper = new AuctionSniper(new Item(ITEM_ID, 234), null);
@Before public void attachModelListener() {
model.addTableModelListener(listener);
}
@Test public void
hasEnoughColumns() {
assertThat(model.getColumnCount(), equalTo(Column.values().length));
}
@Test public void
setsUpColumnHeadings() {
for (Column column: Column.values()) {
assertEquals(column.name, model.getColumnName(column.ordinal()));
}
}
@Test public void
acceptsNewSniper() {
context.checking(new Expectations() {{
one(listener).tableChanged(with(anInsertionAtRow(0)));
}});
model.sniperAdded(sniper);
assertRowMatchesSnapshot(0, SniperSnapshot.joining(ITEM_ID));
}
@Test public void
setsSniperValuesInColumns() {
SniperSnapshot bidding = sniper.getSnapshot().bidding(555, 666);
context.checking(new Expectations() {{
allowing(listener).tableChanged(with(anyInsertionEvent()));
one(listener).tableChanged(with(aChangeInRow(0)));
}});
model.sniperAdded(sniper);
model.sniperStateChanged(bidding);
assertRowMatchesSnapshot(0, bidding);
}
@Test public void
notifiesListenersWhenAddingASniper() {
context.checking(new Expectations() { {
one(listener).tableChanged(with(anInsertionAtRow(0)));
}});
assertEquals(0, model.getRowCount());
model.sniperAdded(sniper);
assertEquals(1, model.getRowCount());
assertRowMatchesSnapshot(0, SniperSnapshot.joining(ITEM_ID));
}
@Test public void
holdsSnipersInAdditionOrder() {
AuctionSniper sniper2 = new AuctionSniper(new Item("item 1", 345), null);
context.checking(new Expectations() { {
ignoring(listener);
}});
model.sniperAdded(sniper);
model.sniperAdded(sniper2);
assertEquals(ITEM_ID, cellValue(0, Column.ITEM_IDENTIFIER));
assertEquals("item 1", cellValue(1, Column.ITEM_IDENTIFIER));
}
@Test public void
updatesCorrectRowForSniper() {
AuctionSniper sniper2 = new AuctionSniper(new Item("item 1", 345), null);
context.checking(new Expectations() { {
allowing(listener).tableChanged(with(anyInsertionEvent()));
one(listener).tableChanged(with(aChangeInRow(1)));
}});
model.sniperAdded(sniper);
model.sniperAdded(sniper2);
SniperSnapshot winning1 = sniper2.getSnapshot().winning(123);
model.sniperStateChanged(winning1);
assertRowMatchesSnapshot(1, winning1);
}
@Test(expected=Defect.class) public void
throwsDefectIfNoExistingSniperForAnUpdate() {
model.sniperStateChanged(new SniperSnapshot("item 1", 123, 234, SniperState.WINNING));
}
private void assertRowMatchesSnapshot(int row, SniperSnapshot snapshot) {
assertEquals(snapshot.itemId, cellValue(row, Column.ITEM_IDENTIFIER));
assertEquals(snapshot.lastPrice, cellValue(row, Column.LAST_PRICE));
assertEquals(snapshot.lastBid, cellValue(row, Column.LAST_BID));
assertEquals(SnipersTableModel.textFor(snapshot.state), cellValue(row, Column.SNIPER_STATE));
}
private Object cellValue(int rowIndex, Column column) {
return model.getValueAt(rowIndex, column.ordinal());
}
Matcher anyInsertionEvent() {
return hasProperty("type", equalTo(TableModelEvent.INSERT));
}
Matcher anInsertionAtRow(final int row) {
return samePropertyValuesAs(new TableModelEvent(model, row, row, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
}
private Matcher aChangeInRow(int row) {
return samePropertyValuesAs(new TableModelEvent(model, row));
}
}
================================================
FILE: test/unit/test/auctionsniper/xmpp/AuctionMessageTranslatorTest.java
================================================
package test.auctionsniper.xmpp;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.packet.Message;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.junit.Test;
import org.junit.runner.RunWith;
import auctionsniper.AuctionEventListener;
import auctionsniper.AuctionEventListener.PriceSource;
import auctionsniper.xmpp.AuctionMessageTranslator;
import auctionsniper.xmpp.XMPPFailureReporter;
@RunWith(JMock.class)
public class AuctionMessageTranslatorTest {
private static final String SNIPER_ID = "sniper id";
private static final Chat UNUSED_CHAT = null;
private final Mockery context = new Mockery();
private final XMPPFailureReporter failureReporter = context.mock(XMPPFailureReporter.class);
private final AuctionEventListener listener = context.mock(AuctionEventListener.class);
private final AuctionMessageTranslator translator = new AuctionMessageTranslator(SNIPER_ID, listener, failureReporter);
@Test public void
notifiesAuctionClosedWhenCloseMessageReceived() {
context.checking(new Expectations() {{
exactly(1).of(listener).auctionClosed();
}});
Message message = new Message();
message.setBody("SOLVersion: 1.1; Event: CLOSE;");
translator.processMessage(UNUSED_CHAT, message);
}
@Test public void
notifiesBidDetailsWhenCurrentPriceMessageReceivedFromOtherBidder() {
context.checking(new Expectations() {{
exactly(1).of(listener).currentPrice(192, 7, PriceSource.FromOtherBidder);
}});
Message message = new Message();
message.setBody("SOLVersion: 1.1; Event: PRICE; CurrentPrice: 192; Increment: 7; Bidder: Someone else;");
translator.processMessage(UNUSED_CHAT, message);
}
@Test public void
notifiesBidDetailsWhenCurrentPriceMessageReceivedFromSniper() {
context.checking(new Expectations() {{
exactly(1).of(listener).currentPrice(192, 7, PriceSource.FromSniper);
}});
Message message = new Message();
message.setBody("SOLVersion: 1.1; Event: PRICE; CurrentPrice: 192; Increment: 7; Bidder: "
+ SNIPER_ID + ";");
translator.processMessage(UNUSED_CHAT, message);
}
@Test public void
notifiesAuctionFailedWhenBadMessageReceived() {
String badMessage = "a bad message";
expectFailureWithMessage(badMessage);
translator.processMessage(UNUSED_CHAT, message(badMessage));
}
@Test public void
notifiesAuctionFailedWhenEventTypeMissing() {
String badMessage = "SOLVersion: 1.1; CurrentPrice: 234; Increment: 5; Bidder: " + SNIPER_ID + ";";
expectFailureWithMessage(badMessage);
translator.processMessage(UNUSED_CHAT, message(badMessage));
}
private Message message(String body) {
Message message = new Message();
message.setBody(body);
return message;
}
private void expectFailureWithMessage(final String badMessage) {
context.checking(new Expectations() {{
oneOf(listener).auctionFailed();
oneOf(failureReporter).cannotTranslateMessage(
with(SNIPER_ID), with(badMessage),
with(any(Exception.class)));
}});
}
}
================================================
FILE: test/unit/test/auctionsniper/xmpp/LoggingXMPPFailureReporterTest.java
================================================
package test.auctionsniper.xmpp;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.AfterClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import auctionsniper.xmpp.LoggingXMPPFailureReporter;
@RunWith(JMock.class)
public class LoggingXMPPFailureReporterTest {
private final Mockery context = new Mockery() {{
setImposteriser(ClassImposteriser.INSTANCE);
}};
final Logger logger = context.mock(Logger.class);
final LoggingXMPPFailureReporter reporter = new LoggingXMPPFailureReporter(logger);
@AfterClass public static void resetLogging() {
LogManager.getLogManager().reset();
}
@Test public void
writesMessageTranslationFailureToLog() {
context.checking(new Expectations() {{
oneOf(logger).severe(" "
+ "Could not translate message \"bad message\" "
+ "because \"java.lang.Exception: an exception\"");
}});
reporter.cannotTranslateMessage("auction id", "bad message",
new Exception("an exception"));
}
}
================================================
FILE: tools/openfire-version-is-3.6.txt
================================================