Refactor and expand core functionalities with new modules

Added robust utility modules: Tone generation, image scaling, and sound tone mapping. Developed an encryption domain with new abstractions. Updated and annotated the SQL query class for clarity and functionality. Removed unused `@Config` annotation.
This commit is contained in:
Konrad Neitzel
2025-04-02 22:09:02 +02:00
parent 8694899e57
commit 77ea70f4e6
21 changed files with 2754 additions and 904 deletions
+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.neitzel.lib</groupId>
<artifactId>neitzellib</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>net</artifactId>
<properties>
<!-- Application Properties -->
<link.name>${project.artifactId}</link.name>
<launcher>${project.artifactId}</launcher>
<appName>${project.artifactId}</appName>
<jar.filename>${project.artifactId}-${project.version}</jar.filename>
<!-- Dependency versions -->
<javax.mail.version>1.6.2</javax.mail.version>
</properties>
<dependencies>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>${javax.mail.version}</version>
</dependency>
</dependencies>
<build>
<finalName>${jar.filename}</finalName>
<plugins>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,128 @@
package de.neitzel.net.imap;
import java.util.Objects;
/**
* Represents an IMAP account with server configuration, user credentials, and protocol details.
* This class provides methods to set or get the account details and overrides methods for string
* representation, equality checks, and hashing.
*/
public class ImapAccount {
/**
* Specifies the server address for the IMAP account.
* This typically represents the hostname or IP address
* of the mail server that handles IMAP communication.
*/
protected String server;
/**
* Retrieves the server address associated with this IMAP*/
public String getServer() { return server; }
/**
* Sets the server address for the IMAP account.
*
* @param server the server address to be configured for the IMAP account
*/
public void setServer(String server) { this.server = server; }
/**
* Represents the username or identifier used to authenticate the IMAP account.
* This value is associated with the user's credentials required to log in
* to the IMAP server.
*/
protected String user;
/**
* Retrieves the username associated with this IMAP account.
*
* @return the username of the IMAP account as a String.
*/
public String getUser() { return user; }
/**
* Sets the username associated with the IMAP account.
*
* @param user The username to be set for this IMAP account.
*/
public void setUser(String user) { this.user = user; }
/**
* The password associated with the IMAP account.
* This is used to authenticate the user when connecting to the IMAP server.
*/
protected String password;
/**
* Retrieves the password associated with this IMAP account.
*
* @return the password of the IMAP account.
*/
public String getPassword() { return password; }
/**
* Sets the password for the IMAP account.
*
* @param password The password to be set for the account.
*/
public void setPassword(String password) { this.password = password; }
/**
* Specifies the protocol used for the IMAP account, such as "IMAP" or "IMAPS".
* This field determines the communication protocol for interacting with the server.
*/
protected String protocol;
/**
* Retrieves the protocol used by this IMAP account.
*
* @return the protocol as a String, which defines the communication method for the IMAP account.
*/
public String getProtocol() { return protocol; }
/**
* Sets the protocol used by the IMAP account.
* @param protocol the protocol to be used, e.g., "IMAP" or "IMAPS".
*/
public void setProtocol(String protocol) { this.protocol = protocol; }
/**
* Provides a string representation of the ImapAccount object including its server, user,
* a masked password, and protocol information. The password is represented as '########' if it is not empty.
*
* @return A formatted string that describes the ImapAccount object with its server, user, masked password, and protocol.
*/
@Override public String toString() {
return String.format("ImapAccount(%s, %s, %s, %s",
server,
user,
password.length()==0 ? "''" : "########" ,
protocol);
}
/**
* Computes the hash code for this instance of the ImapAccount class.
* The hash code is based on the values of the server, user, password, and protocol fields.
* This ensures that instances with the same field values produce the same hash code.
*
* @return An integer hash code representing this ImapAccount instance.
*/
@Override public int hashCode() {
return Objects.hash(server, user, password, protocol);
}
/**
* Compares this ImapAccount instance with the specified object for equality.
* The comparison is based on the equality of all relevant fields: server, user, password, and protocol.
*
* @param obj the object to compare with this instance
* @return true if the specified object is equal to this ImapAccount instance; false otherwise
*/
@Override public boolean equals(Object obj) {
// Check type of argument, includes check of null.
if (!(obj instanceof ImapAccount)) return false;
// Check Reference equals
if (this == obj) return true;
// Check the comparison of all field
ImapAccount other = (ImapAccount) obj;
return (server == other.server || (server != null && server.equals(other.server))) &&
(user == other.user || (user != null && user.equals(other.user))) &&
(password == other.password || (password != null && password.equals(other.password))) &&
(protocol == other.protocol || (protocol != null && protocol.equals(other.protocol)));
}
}
@@ -0,0 +1,256 @@
package de.neitzel.net.imap;
import lombok.extern.slf4j.Slf4j;
import javax.mail.*;
import java.util.*;
/**
* The ImapAccountMonitor class is responsible for monitoring an IMAP email account
* by maintaining a connection to the server, tracking specific folders, and
* listening for new emails. This class allows managing the set of monitored folders,
* processing unseen and undeleted messages, and notifying registered listeners when
* new messages are received.
*/
@Slf4j
public class ImapAccountMonitor {
/**
* Represents an instance of an IMAP account, encapsulating details about
* server configuration, user credentials, and protocol settings.
* This variable is used to connect and authenticate the application's interaction
* with an IMAP email server.
*/
protected ImapAccount account;
/**
* Represents the current session for interaction with an IMAP server.
* The session encapsulates the configuration and context required
* to establish a connection and perform operations on the IMAP server.
* It is initialized and utilized internally within the application.
*/
protected Session session;
/**
* Represents the mail store associated with the IMAP account.
* The store is used to interact with the IMAP server for email retrieval and management.
*/
protected Store store;
/**
* Represents a collection of folders associated with an IMAP account.
* The map's keys are folder names (String), and the values are Folder objects.
* This variable is used to store and manage the hierarchy or collection
* of email folders for an IMAP account.
*/
protected Map<String, Folder> folders = new HashMap<String, Folder>();
/**
* Monitors an IMAP account by establishing a connection to the specified mail server
* using the provided account credentials and protocol.
*
* @param account the IMAP account containing details such as server, user, password, and protocol
* @throws NoSuchProviderException if the specified mail store protocol is not available
* @throws MessagingException if the connection to the mail server fails
*/
public ImapAccountMonitor(ImapAccount account) throws NoSuchProviderException, MessagingException {
log.trace("Constructor ({})", account.toString());
this.account = account;
Properties props = System.getProperties();
props.setProperty("mail.store.protocol", account.getProtocol());
session = Session.getDefaultInstance(props);
try {
store = session.getStore("imaps");
store.connect(account.server, account.user, account.password);
} catch (NoSuchProviderException ex) {
log.error("Unable to get imaps Store.", ex);
throw ex;
} catch (MessagingException ex) {
log.error("Unable to connect.", ex);
throw ex;
}
log.trace("Constructor done.");
}
/**
* Closes the resources associated with the current instance, including
* IMAP folders and the store. The method ensures that all folders are
* closed properly, followed by the closure of the store.
*
* Any exceptions encountered during the closure of folders or the store
* are caught and logged without interrupting the overall closing process.
* This is to ensure that all resources are attempted to be closed even if
* an error occurs with one of them.
*
* The method logs the start and end of the closing process for traceability.
*/
public void close() {
log.trace("close() called.");
// Close the folders
for (Folder folder: folders.values()) {
try {
folder.close(false);
} catch (MessagingException ex) {
// Only log the exception.
log.warn("Exception when closing folder.", ex);
}
}
// Close the store
try {
store.close();
} catch (MessagingException ex) {
// Only log the error.
log.warn("Exception when closing the store.", ex);
}
log.trace("close() call ended.");
}
/**
* Adds a new folder to the IMAP account and opens it in read-write mode.
* The folder is added to an internal collection for future access.
*
* @param name the name of the folder to be added and opened
* @throws MessagingException if any error occurs while accessing or opening the folder
*/
public void addFolder(String name) throws MessagingException {
log.trace("addFolder(%s) called.", name);
Folder folder = store.getFolder(name);
folder.open(Folder.READ_WRITE);
folders.put(name, folder);
log.trace("addFolder({}) call ended.", name);
}
/**
* Removes the folder with the specified name from the account's folder list.
* If the folder is not found, the method does nothing.
* After removing the folder, it attempts to close any associated resources.
*
* @param name the name of the folder to be removed
*/
public void removeFolder(String name) {
log.trace("removeFolder({}) called.", name);
// Validate folder is known.
if (!folders.containsKey(name)) return;
Folder folder = folders.get(name);
folders.remove(name);
try {
folder.close(false);
} catch (MessagingException ex) {
// TODO: Handle exception ...
}
log.trace("removeFolder({}) call ended.", name);
}
/**
* Checks all monitored folders for new email messages, processes unseen and undeleted messages,
* and raises a NewMailEvent for each new email discovered. The method ensures processed messages
* are marked as seen.
*
* The method iterates through all folders currently being monitored, fetching their messages and
* evaluating the state of each. If a message is neither marked as seen nor deleted, it is marked
* as seen and a NewMailEvent is raised for it.
*
* Proper error handling is implemented to log any MessagingException that occurs while processing
* the messages of a folder without interrupting the processing of other folders.
*
* Note: This method relies on external logging (via `log`) and appropriate event handling
* via `raiseNewEmailEvent`. The `folders` collection holds the monitored folders required.
*
* The folder processing stops only in case of irrecoverable exceptions outside this method's scope.
*/
public void check() {
log.trace("check() called.");
// Loop through all folders that are monitored
for (Folder folder: folders.values()) {
try {
// Loop through all messages.
Message[] messages = folder.getMessages();
for (Message message: messages) {
// Check if message wasn't seen and wasn't deleted
if (!message.isSet(Flags.Flag.SEEN) && !message.isSet(Flags.Flag.DELETED)) {
// Mark message as seen.
message.setFlag(Flags.Flag.SEEN, true);
// Raise NewMailEvent
raiseNewEmailEvent(message);
}
}
} catch (MessagingException ex) {
log.error("Exception when reading messages of folder {}.", folder.getName(), ex);
// So far no handling of this error except logging it.
}
}
log.trace("check() call ended.");
}
/**
* A collection of listeners that respond to IMAP-related events.
* Each listener in this collection can perform specific actions when triggered
* by events such as receiving new email notifications.
*/
private Collection<ImapListener> imapListeners = new ArrayList<ImapListener>();
/**
* Adds an IMAP listener to receive notifications about IMAP-related events such as new email arrivals.
*
* @param listener the ImapListener instance to be added to the list of listeners
*/
public void addImapListener(ImapListener listener) {
log.trace("addImapListener() called!");
imapListeners.add(listener);
}
/**
* Removes the specified IMAP listener from the list of active listeners.
* If the listener is present, it will be removed; otherwise, no action is taken.
*
* @param listener The IMAP listener to be removed.
*/
public void removeImapListener(ImapListener listener) {
log.trace("removeImapListener() called!");
if (imapListeners.contains(listener)) {
log.info("Removing the IMAP listener.");
imapListeners.remove(listener);
}
}
/**
* Raises a new email event and notifies all registered listeners.
* The method loops through the listeners and invokes their {@code NewEmailReceived}
* method with a newly created {@link NewEmailEvent}.
* If a listener marks the event as handled, the remaining listeners will
* not be notified.
*
* @param message The email message that triggered the event. This message
* is included in the {@link NewEmailEvent} passed to the listeners.
*/
protected void raiseNewEmailEvent(Message message) {
log.trace("raiseNewEmailEvent() called!");
// Create the event.
NewEmailEvent event = new NewEmailEvent(this, message);
// Call all listeners.
for (ImapListener listener: imapListeners) {
log.trace("Calling listener in {} ....", listener.getClass().getName());
listener.newEmailReceived(event);
// Check if the event was handled so no further ImaListeners will be called.
if (event.isHandled()) {
log.trace("raiseNewEmailEvent(): Breaking out of listener loop because event was handled.");
break;
}
}
log.trace("raiseNewEmailEvent() call ended!");
}
}
@@ -0,0 +1,17 @@
package de.neitzel.net.imap;
/**
* Listener interface for receiving notifications about new emails.
* Classes interested in processing new emails should implement this interface and
* register themselves to receive events via supported mechanisms.
*/
public interface ImapListener {
/**
* This method gets invoked when a new email is received. Implementers of this method
* handle the processing of new email events triggered by the associated source.
*
* @param event the NewEmailEvent object that contains information about the received email,
* including the email message and its source.
*/
void newEmailReceived(NewEmailEvent event);
}
@@ -0,0 +1,48 @@
package de.neitzel.net.imap;
import lombok.Getter;
import lombok.Setter;
import javax.mail.Message;
import java.util.EventObject;
/**
* Event class representing the occurrence of a new email.
* It is typically used to encapsulate information about the received email
* and provide a means for event listeners to handle the event.
*/
public class NewEmailEvent extends EventObject {
/**
* Represents the email message associated with the NewEmailEvent.
* This field encapsulates the content and metadata of the email
* received, allowing event listeners to process the message details.
*/
@Getter
@Setter
protected Message message;
/**
* Indicates whether the event has already been handled or processed.
* When true, it signifies that the event has been handled and no further
* processing is required. It is used to prevent multiple handling
* of the same event.
*/
@Getter
@Setter
protected boolean handled = false;
/**
* Constructs a new NewEmailEvent instance.
* This event encapsulates information about a newly received email,
* including its associated message and the source of the event.
*
* @param source the object on which the event initially occurred; typically represents
* the source of the email event, such as an email client or server.
* @param message the Message object representing the email associated with this event.
*/
public NewEmailEvent(Object source, Message message) {
super(source);
this.message = message;
}
}
@@ -0,0 +1,82 @@
package de.neitzel.net.mail;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.ContentType;
import javax.mail.internet.MimeMultipart;
import java.io.IOException;
/**
* Utility functions to use with javax.mail.Message instances.
*/
public class MessageUtils {
/**
* Gets the text of the email message.
* @param message Message to get the text from.
* @return Text of the email message.
* @throws IOException
* @throws MessagingException
*/
public static String getTextFromMessage(Message message) throws IOException, MessagingException {
String result = "";
if (message.isMimeType("text/plain")) {
result = message.getContent().toString();
} else if (message.isMimeType("multipart/*")) {
MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();
result = getTextFromMimeMultipart(mimeMultipart);
}
return result;
}
/**
* Gets the text of a MimeMultipart.
* @param mimeMultipart MimeMultipart to get the text from.
* @return Text of the MimeMultipart instance.
* @throws IOException
* @throws MessagingException
*/
private static String getTextFromMimeMultipart(
MimeMultipart mimeMultipart) throws IOException, MessagingException {
int count = mimeMultipart.getCount();
if (count == 0)
throw new MessagingException("Multipart with no body parts not supported.");
boolean multipartAlt = new ContentType(mimeMultipart.getContentType()).match("multipart/alternative");
if (multipartAlt)
// alternatives appear in an order of increasing
// faithfulness to the original content. Customize as req'd.
return getTextFromBodyPart(mimeMultipart.getBodyPart(count - 1));
String result = "";
for (int i = 0; i < count; i++) {
BodyPart bodyPart = mimeMultipart.getBodyPart(i);
result += getTextFromBodyPart(bodyPart);
}
return result;
}
/**
* Gets the text of a BodyPart
* @param bodyPart BodyPart to get text from.
* @return Text of body part or empty string if not a known type.
* @throws IOException
* @throws MessagingException
*/
private static String getTextFromBodyPart(
BodyPart bodyPart) throws IOException, MessagingException {
String result = "";
if (bodyPart.isMimeType("text/plain")) {
result = (String) bodyPart.getContent();
} else if (bodyPart.isMimeType("text/html")) {
result = (String) bodyPart.getContent();
//String html = (String) bodyPart.getContent();
// Not parsing the html right now!
// result = org.jsoup.Jsoup.parse(html).text();
} else if (bodyPart.getContent() instanceof MimeMultipart){
result = getTextFromMimeMultipart((MimeMultipart)bodyPart.getContent());
}
return result;
}
}