У меня есть почтовое приложение, в котором пользователь вручную получает почту с сервера, каждый раз, когда это происходит, почта сохраняется в ObservableArrayList. Этот список связан с ListView через конструктор ListView (ObservableList). SelectionModel ListView настроен для отображения электронной почты, которая в данный момент выбрана на панели справа, с подробной информацией выше.
Проблема возникает, если пользователь когда-либо пытается получить второй набор электронных писем. Основная модель выбора, по-видимому, не соответствует обновленному списку, и нажатие любого элемента в списке заполнит зрителя информацией из сообщения, которое ранее занимало эту позицию. Это не проблема при извлечении сообщения, поскольку предварительный просмотр ячейки отражает изменение. Я пробовал почти каждую комбинацию методов для сброса базовой модели выбора, но на данный момент у меня ничего не вышло.
Возможно, связанная с этим ошибка (хотя и сомневаюсь в этом) заключается в том, что при попытке удалить сообщение электронной почты выбрасываются десятки идентичных исключения и причины такого десин c.
Минимальный воспроизводимый пример (Требуется javax.JavaMail):
артефакт Maven для javamail
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
Основной класс
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javax.mail.*;
import java.util.HashMap;
import java.util.Properties;
public class TagMailMain extends Application {
public static final HashMap<String, Account> accounts = new HashMap<>();
public static void main(final String[] args) {
launch(args);
}
// Add elements
private final Pane root = new Pane();
private final
Scene primaryScene = new Scene(root);
private Account account;
private boolean hasMailBeenChecked = false;
private Store store;
private Folder emailFolder;
private final ObservableList<Message> messages = FXCollections.observableArrayList();
private final ListView<Message> messageList = new ListView<>(messages);
private final WebView messageView = new WebView();
private void fetch(final String host, final String storeType, final String user, final String password) {
final Task<Message[]> task = new Task<Message[]>() {
@Override
public Message[] call() {
try {
if (hasMailBeenChecked) {
// close the store and folder objects
emailFolder.close(true);
store.close();
}
// create properties field
final Properties properties = new Properties();
properties.put("mail.store.protocol", storeType);
properties.put("mail." + storeType + ".host", host);
if (storeType.contains("pop"))
properties.put("mail." + storeType + ".port", "995");
else if (storeType.contains("imap")) properties.put("mail." + storeType + ".port", "993");
properties.put("mail." + storeType + ".ssl.enable", "true");
final Session emailSession = Session.getDefaultInstance(properties);
// create the store object and connect with the storeType server
store = emailSession.getStore(storeType);
store.connect(host, user, password);
// create the folder object and open it
emailFolder = store.getFolder("INBOX");
emailFolder.open(Folder.READ_WRITE);
return emailFolder.getMessages();
} catch (final Exception e) {
e.printStackTrace();
}
return null;
}
};
task.setOnSucceeded(e -> {
messages.clear();
messageList.getItems().clear();
messages.setAll(task.getValue());
messages.sort((o1, o2) -> {
try {
if (o1.getSentDate() == (o2.getSentDate()))
return 0;
else if (o1.getSentDate().getTime() < o2.getSentDate().getTime())
return 1;
else
return -1;
} catch (final MessagingException e1) {
e1.printStackTrace();
return 0;
}
});
hasMailBeenChecked = true;
messageList.refresh();
});
final Thread thread = new Thread(task);
thread.start();
}
@Override
public void init() throws Exception {
Button editAccount = new Button("Accounts");
editAccount.setOnAction((e) -> {
final TagMailAccountsEdit accountsWindow = new TagMailAccountsEdit();
accountsWindow.init();
});
Button checkMail = new Button("Check mail");
checkMail.setOnAction(e -> {
messageList.getSelectionModel().clearSelection();
final ChoiceDialog<Account> accountChooser = new ChoiceDialog<>(null, accounts.values());
accountChooser.setHeaderText("Select an account to check mail");
final Stage dialogStage = (Stage) accountChooser.getDialogPane().getScene().getWindow();
if (showAccountChooser(accountChooser))
return;
if (account.getRetrievalProtocol() == Protocol.IMAP)
fetch(account.getServerIMAP(), "imap", account.getUsername(), account.getPassword());
else if (account.getRetrievalProtocol() == Protocol.POP3)
fetch(account.getServerPOP3(), "pop3", account.getUsername(), account.getPassword());
});
// Center
final Label toLabel = new Label("To: ");
final Label fromLabel = new Label("From: ");
final Label subjectLabel = new Label("Subject: ");
final Label dateLabel = new Label("Received: ");
final VBox messageViewBox = new VBox(toLabel, fromLabel, subjectLabel, dateLabel, messageView);
final HBox center = new HBox(messageList, messageViewBox);
final BorderPane mainPane = new BorderPane(center, editAccount, null, checkMail, null);
root.getChildren().add(mainPane);
messageList.setCellFactory(list -> new MessageCell());
messageList.getSelectionModel().selectedItemProperty().addListener(e -> {
final Message m = messageList.getSelectionModel().getSelectedItem();
if (m != null) {
try {
m.setFlag(Flags.Flag.SEEN, true);
StringBuilder recipients = new StringBuilder();
for (final Address i : m.getAllRecipients()) recipients.append(i.toString());
toLabel.setText("To: " + recipients);
StringBuilder from = new StringBuilder();
for (final Address i : m.getFrom()) from.append(i.toString());
fromLabel.setText("From: " + from);
subjectLabel.setText("Subject: " + m.getSubject());
dateLabel.setText("Received: " + m.getSentDate().toString());
messageView.getEngine().loadContent(mimeToHTML(m));
} catch (MessagingException e1) {
e1.printStackTrace();
}
}
});
}
private boolean showAccountChooser(ChoiceDialog<Account> accountChooser) {
accountChooser.showAndWait();
account = accountChooser.getResult();
if (account == null)
return true;
if (account.getPassword() == null) {
Alert alert = new Alert(AlertType.ERROR, "No password, please log in", ButtonType.CLOSE);
alert.showAndWait();
return true;
}
return false;
}
private String mimeToHTML(final Part m) {
return"";
}
@Override
public void start(final Stage primaryStage) throws Exception {
primaryStage.setTitle("TagMail");
primaryStage.setResizable(false);
primaryStage.setScene(primaryScene);
primaryStage.show();
}
}
Пользовательская ячейка для отображения объекта Message в ListView
import javafx.scene.control.ListCell;
import javax.mail.Message;
import javax.mail.MessagingException;
public class MessageCell extends ListCell<Message> {
public MessageCell() {
}
@Override
protected void updateItem(Message message, boolean empty) {
super.updateItem(message, empty);
if (getText() == null) {
getListView().getSelectionModel().selectedItemProperty().addListener(e -> {
if (getListView().getSelectionModel().getSelectedItem().equals(getItem())) setGraphic(null); //TODO fix this hacky solution to mark mail as read
});
if (empty || message == null) {
setText(null);
setGraphic(null);
} else {
try {
setText(message.getFrom()[0].toString() + "\n" + message.getSubject());
} catch (MessagingException e) {
e.printStackTrace();
}
}
}
}
}
Объект, представляющий учетную запись электронной почты
import javafx.util.Pair;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.Serializable;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
public class Account implements Serializable {
private static final long serialVersionUID = 2;
private final String name;
private Pair<byte[], byte[]> passwordHash; // hash, salt
private transient String password;
private final String username;
private final String serverSMTP;
private final String serverIMAP;
private final String serverPOP3;
private final Protocol retrievalProtocol;
public Account(String name, String password, String username, String serverSMTP, String serverIMAP,
String serverPOP3, Protocol retrievalProtocol) {
this.name = name;
setPassword(password);
this.username = username;
this.serverSMTP = serverSMTP;
this.serverIMAP = serverIMAP;
this.serverPOP3 = serverPOP3;
this.retrievalProtocol = retrievalProtocol;
}
private Pair<byte[], byte[]> generateHash(String toHash) {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
KeySpec spec = new PBEKeySpec(toHash.toCharArray(), salt, 65536, 128);
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] hash = factory.generateSecret(spec).getEncoded();
return new Pair<>(hash, salt);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
public String getName() {
return name;
}
public boolean checkPassword(String passwordToCheck) {
byte[] hash = passwordHash.getKey();
byte[] salt = passwordHash.getValue();
KeySpec spec = new PBEKeySpec(passwordToCheck.toCharArray(), salt, 65536, 128);
byte[] hashToCheck=null;
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
hashToCheck=factory.generateSecret(spec).getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
}
return Arrays.equals(hash, hashToCheck);
}
public Protocol getRetrievalProtocol() {
return retrievalProtocol;
}
public String getServerIMAP() {
return serverIMAP;
}
public String getServerPOP3() {
return serverPOP3;
}
public String getServerSMTP() {
return serverSMTP;
}
public String getUsername() {
return username;
}
public void setPasswordHash(String password) {
passwordHash = generateHash(password);
}
public Pair<byte[], byte[]> getPasswordHash() {
return passwordHash;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
setPasswordHash(password);
}
@Override
public String toString() {
return getName();
}
}
enum Protocol {
IMAP, POP3
}
Просто редактор объекта Account, возможно не имеет отношения к ошибке
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Optional;
public class TagMailAccountsEdit {
// Add elements
private final StackPane root = new StackPane();
private final Scene primaryScene = new Scene(root);
private final Stage primaryStage = new Stage();
private final VBox accountColumn = new VBox();
private final VBox buttonColumn = new VBox();
private final HBox accountList = new HBox(accountColumn, new Label("\t\t"), buttonColumn);
private final Button newAccountButton = new Button("New");
private final VBox mainBox = new VBox(accountList, newAccountButton);
public Account editAccount(Account a) {
Dialog<Account> dialog = new Dialog<>();
dialog.setTitle("Account Edit Dialog");
dialog.setHeaderText("Edit any details of your account here");
// Set the button types.
dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
// Create the username and password labels and fields.
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(20, 150, 10, 10));
TextField name = new TextField(a.getName());
name.setPromptText("Name");
TextField username = new TextField(a.getUsername());
username.setPromptText("Username");
PasswordField password = new PasswordField();
password.setPromptText("Password");
TextField serverIMAP = new TextField(a.getServerIMAP());
serverIMAP.setPromptText("IMAP server");
TextField serverPOP3 = new TextField(a.getServerPOP3());
serverPOP3.setPromptText("POP3 server");
TextField serverSMTP = new TextField(a.getServerSMTP());
serverSMTP.setPromptText("SMTP server");
ComboBox<Protocol> retrieveProtocolBox = new ComboBox<>();
retrieveProtocolBox.getItems().add(0, Protocol.IMAP);
retrieveProtocolBox.getItems().add(1, Protocol.POP3);
retrieveProtocolBox.getSelectionModel().select(a.getRetrievalProtocol());
grid.add(new Label("Name:"), 0, 0);
grid.add(name, 1, 0);
grid.add(new Label("Username:"), 0, 1);
grid.add(username, 1, 1);
grid.add(new Label("Password:"), 0, 2);
grid.add(password, 1, 2);
grid.add(new Label("IMAP Server:"), 0, 3);
grid.add(serverIMAP, 1, 3);
grid.add(new Label("POP3 Server:"), 0, 4);
grid.add(serverPOP3, 1, 4);
grid.add(new Label("SMTP Server:"), 0, 5);
grid.add(serverSMTP, 1, 5);
grid.add(new Label("Default:"), 0, 6);
grid.add(retrieveProtocolBox, 1, 6);
dialog.getDialogPane().setContent(grid);
// Request focus on the username field by default.
Platform.runLater(username::requestFocus);
// Convert the result to a username-password-pair when the login button is
// clicked.
dialog.setResultConverter(dialogButton -> new Account(name.getText(), password.getText(), username.getText(), serverSMTP.getText(),
serverIMAP.getText(), serverPOP3.getText(),
retrieveProtocolBox.getSelectionModel().getSelectedItem()));
Optional<Account> result = dialog.showAndWait();
return result.orElse(null);
}
public void init() {
// Setup window
root.setPrefHeight(64);
root.setPrefWidth(512);
mainBox.setSpacing(5);
mainBox.setFillWidth(true);
root.getChildren().add(mainBox);
root.setBackground(new Background(new BackgroundFill(Color.LIGHTGRAY, CornerRadii.EMPTY, Insets.EMPTY)));
accountList.setBackground(new Background(new BackgroundFill(Color.DARKGRAY, CornerRadii.EMPTY, Insets.EMPTY)));
refresh();
newAccountButton.setOnAction(event -> {
TextInputDialog nameOfAccountDialog = new TextInputDialog();
nameOfAccountDialog.setHeaderText("Enter a name for the account");
nameOfAccountDialog.setContentText(null);
nameOfAccountDialog.showAndWait();
while (TagMailMain.accounts.containsKey(nameOfAccountDialog.getResult())) {
nameOfAccountDialog
.setContentText("An account by that name already exists, please enter a different name");
nameOfAccountDialog.showAndWait();
}
if (nameOfAccountDialog.getResult() == null) {
System.out.println("Unknown behavior in nameOfAccount Dialog"); //TODO refine
} else {
TagMailMain.accounts.put(nameOfAccountDialog.getResult(),
editAccount(new Account(nameOfAccountDialog.getResult(), "", "", "", "", "", Protocol.IMAP)));
}
refresh();
});
start();
}
private void refresh() {
accountColumn.getChildren().clear();
buttonColumn.getChildren().clear();
for (String k : TagMailMain.accounts.keySet()) {
Account a = TagMailMain.accounts.get(k);
Button edit = new Button("Edit");
Button delete = new Button("Delete");
accountColumn.getChildren().add(new HBox(new Label(a.getName() + ": " + a.getUsername())));
buttonColumn.getChildren().add(new HBox(edit, delete));
edit.setOnAction(editEvent -> {
Account replacement = editAccount(a);
TagMailMain.accounts.remove(k);
TagMailMain.accounts.put(replacement.getName(), replacement);
refresh();
});
delete.setOnAction(deleteEvent -> {
TagMailMain.accounts.remove(k);
refresh();
});
}
}
public void start() {
primaryStage.setTitle("Accounts edit");
primaryStage.setScene(primaryScene);
primaryStage.show();
}
}