diff --git a/emmas/pom.xml b/emmas/pom.xml
index 81ea17b..485d1f6 100644
--- a/emmas/pom.xml
+++ b/emmas/pom.xml
@@ -80,10 +80,23 @@
nz.ac.waikato.cms.weka
- weka-stable
- 3.8.6
+ weka-dev
+ 3.9.6
+
+ nz.ac.waikato.cms.weka
+ SMOTE
+ 1.0.3
+
+
+
+ nz.ac.waikato.cms.weka
+ naiveBayesTree
+ 1.0.2
+
+
+
jakarta.activation
@@ -114,6 +127,11 @@
4.13.1
test
+
+ org.mindrot
+ jbcrypt
+ 0.4
+
diff --git a/emmas/src/main/java/dev/brianweloba/dao/EventDAO.java b/emmas/src/main/java/dev/brianweloba/dao/EventDAO.java
index 2713063..5ba07f5 100644
--- a/emmas/src/main/java/dev/brianweloba/dao/EventDAO.java
+++ b/emmas/src/main/java/dev/brianweloba/dao/EventDAO.java
@@ -1,13 +1,13 @@
package dev.brianweloba.dao;
-import java.util.List;
-
import dev.brianweloba.model.Event;
import dev.brianweloba.util.HibernateUtil;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional;
+import java.util.List;
+
public class EventDAO {
@Transactional
@@ -28,7 +28,7 @@ public Event create(Event event) {
}
}
- public int countAll(){
+ public int countAll() {
try (EntityManager manager = HibernateUtil.getEntityManager()) {
return manager.createQuery("SELECT COUNT(e) FROM Event e", Long.class)
.getSingleResult()
@@ -36,7 +36,7 @@ public int countAll(){
}
}
- public List findPaginated(int startIndex, int pageSize){
+ public List findPaginated(int startIndex, int pageSize) {
try (EntityManager manager = HibernateUtil.getEntityManager()) {
return manager.createQuery("SELECT e FROM Event e ORDER BY e.eventDate", Event.class)
.setFirstResult(startIndex)
@@ -48,6 +48,7 @@ public List findPaginated(int startIndex, int pageSize){
public List search(String query) {
try (EntityManager manager = HibernateUtil.getEntityManager()) {
String searchTerm = "%" + query.toLowerCase() + "%";
+
return manager.createQuery(
"SELECT e FROM Event e LEFT JOIN FETCH e.rsvps WHERE " +
" LOWER(e.title) LIKE :query OR" +
@@ -66,7 +67,7 @@ public Event findById(Long id) {
}
}
- public Event findByIdAndToken(Long id,String token){
+ public Event findByIdAndToken(Long id, String token) {
EntityManager manager = HibernateUtil.getEntityManager();
try {
TypedQuery query = manager.createQuery(
@@ -78,36 +79,37 @@ public Event findByIdAndToken(Long id,String token){
System.out.println("Id: " + id);
System.out.println("Query: " + query);
return query.getSingleResult();
- }catch (Exception e) {
+ } catch (Exception e) {
if (manager.getTransaction().isActive()) {
manager.getTransaction().rollback();
}
throw e;
- }finally {
+ } finally {
manager.close();
}
}
- public List findAll() {
+ public List findAll() {
try (EntityManager manager = HibernateUtil.getEntityManager()) {
- TypedQuery query = manager.createQuery(
+ return manager.createQuery(
"SELECT DISTINCT e FROM Event e LEFT JOIN FETCH e.rsvps ORDER BY e.eventDate",
Event.class
- );
- return query.getResultList();
+ ).getResultList();
}
}
+
public void update(Event event, String token) {
EntityManager manager = HibernateUtil.getEntityManager();
try {
- Event eventToUpdate = findByIdAndToken(event.getId(),token);
- if(eventToUpdate == null){
+ Event eventToUpdate = findByIdAndToken(event.getId(), token);
+ if (eventToUpdate == null) {
return;
}
manager.getTransaction().begin();
eventToUpdate.setTitle(event.getTitle());
+ eventToUpdate.setEventHost(event.getEventHost());
eventToUpdate.setEventType(event.getEventType());
eventToUpdate.setEventLocation(event.getEventLocation());
eventToUpdate.setDescription(event.getDescription());
@@ -126,15 +128,25 @@ public void update(Event event, String token) {
}
}
- public void delete(Long id) {
+ public boolean delete(Long id, String token) {
EntityManager manager = HibernateUtil.getEntityManager();
try {
manager.getTransaction().begin();
- Event event = manager.find(Event.class, id);
- if (event != null) {
- manager.remove(event);
+ Event event = manager.createQuery(
+ "SELECT e FROM Event e WHERE e.id = :id AND e.editToken = :token", Event.class)
+ .setParameter("id", id)
+ .setParameter("token", token)
+ .getResultList()
+ .stream()
+ .findFirst()
+ .orElse(null);
+
+ if (event == null) {
+ return false;
}
+ manager.remove(event);
manager.getTransaction().commit();
+ return true;
} catch (Exception e) {
if (manager.getTransaction().isActive()) {
manager.getTransaction().rollback();
diff --git a/emmas/src/main/java/dev/brianweloba/dao/RsvpDAO.java b/emmas/src/main/java/dev/brianweloba/dao/RsvpDAO.java
index 8f8989e..38c5799 100644
--- a/emmas/src/main/java/dev/brianweloba/dao/RsvpDAO.java
+++ b/emmas/src/main/java/dev/brianweloba/dao/RsvpDAO.java
@@ -4,9 +4,12 @@
import dev.brianweloba.model.RSVP;
import dev.brianweloba.util.HibernateUtil;
import jakarta.persistence.EntityManager;
+import jakarta.persistence.NoResultException;
+
+import java.util.List;
public class RsvpDAO {
- public RSVP create(RSVP rsvp,Long eventId) {
+ public void create(RSVP rsvp, Long eventId) {
EntityManager manager = HibernateUtil.getEntityManager();
try {
manager.getTransaction().begin();
@@ -16,23 +19,23 @@ public RSVP create(RSVP rsvp,Long eventId) {
throw new IllegalArgumentException("Event not found");
}
- Long currentGuests = manager.createQuery(
- "SELECT SUM(r.guests) FROM RSVP r WHERE r.event = :event", Long.class)
- .setParameter("event", event)
- .getSingleResult();
+ boolean alreadyExists = checkExistingRsvp(manager, event, rsvp.getEmail());
+ if (alreadyExists) {
+ throw new IllegalArgumentException("Email already registered for this event");
+ }
- currentGuests = currentGuests == null ? 0 : currentGuests;
+ Long currentGuests = getCurrentGuestCount(manager, event);
- int guestCount = rsvp.getGuests();
- if (currentGuests + guestCount > event.getEventCapacity()) {
+ if (currentGuests + rsvp.getGuests() > event.getEventCapacity()) {
throw new IllegalArgumentException("Event has reached capacity");
}
rsvp.setEvent(event);
+ event.getRsvps().add(rsvp);
manager.persist(rsvp);
manager.getTransaction().commit();
- return rsvp;
+
} catch (Exception e) {
if (manager.getTransaction().isActive()) {
manager.getTransaction().rollback();
@@ -42,4 +45,50 @@ public RSVP create(RSVP rsvp,Long eventId) {
manager.close();
}
}
+
+ private boolean checkExistingRsvp(EntityManager manager, Event event, String email) {
+ try {
+ return manager.createQuery(
+ "SELECT COUNT(r) > 0 FROM RSVP r " +
+ "WHERE r.event = :event AND LOWER(r.email) = LOWER(:email)",
+ Boolean.class)
+ .setParameter("event", event)
+ .setParameter("email", email)
+ .getSingleResult();
+ } catch (NoResultException e) {
+ return false;
+ }
+ }
+
+ private Long getCurrentGuestCount(EntityManager manager, Event event) {
+ try {
+ Long count = manager.createQuery(
+ "SELECT SUM(r.guests) FROM RSVP r WHERE r.event = :event",
+ Long.class)
+ .setParameter("event", event)
+ .getSingleResult();
+ return count != null ? count : 0L;
+ } catch (NoResultException e) {
+ return 0L;
+ }
+ }
+
+ public List getEventAttendees(Long eventId) {
+ try (EntityManager manager = HibernateUtil.getEntityManager()) {
+ return manager.createQuery("SELECT r FROM RSVP r WHERE r.event.id = :eventId", RSVP.class)
+ .setParameter("eventId", eventId)
+ .getResultList();
+ }
+ }
+
+ // Additional useful method
+ public Event findEventWithRsvps(Long eventId) {
+ try (EntityManager manager = HibernateUtil.getEntityManager()) {
+ return manager.createQuery(
+ "SELECT e FROM Event e LEFT JOIN FETCH e.rsvps WHERE e.id = :id",
+ Event.class)
+ .setParameter("id", eventId)
+ .getSingleResult();
+ }
+ }
}
\ No newline at end of file
diff --git a/emmas/src/main/java/dev/brianweloba/dao/UserDAO.java b/emmas/src/main/java/dev/brianweloba/dao/UserDAO.java
new file mode 100644
index 0000000..3d73c50
--- /dev/null
+++ b/emmas/src/main/java/dev/brianweloba/dao/UserDAO.java
@@ -0,0 +1,44 @@
+package dev.brianweloba.dao;
+
+import dev.brianweloba.model.User;
+import dev.brianweloba.util.HibernateUtil;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.NoResultException;
+
+public class UserDAO {
+ public User findByUsername(String username) {
+ try (EntityManager manager = HibernateUtil.getEntityManager()) {
+ return manager.createQuery("SELECT u FROM User u WHERE u.username = :username", User.class)
+ .setParameter("username", username)
+ .getSingleResult();
+ } catch (NoResultException e) {
+ return null;
+ }
+ }
+
+ public User findByEmail(String email) {
+ try (EntityManager manager = HibernateUtil.getEntityManager()) {
+ return manager.createQuery("SELECT u FROM User u WHERE u.email = :email", User.class)
+ .setParameter("email", email)
+ .getSingleResult();
+ } catch (NoResultException e) {
+ return null;
+ }
+ }
+
+ public void save(User user) {
+ EntityManager manager = HibernateUtil.getEntityManager();
+ try {
+ manager.getTransaction().begin();
+ manager.persist(user);
+ manager.getTransaction().commit();
+ } catch (Exception e) {
+ if (manager.getTransaction().isActive()) {
+ manager.getTransaction().rollback();
+ }
+ throw e;
+ } finally {
+ manager.close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/emmas/src/main/java/dev/brianweloba/filter/AuthFilter.java b/emmas/src/main/java/dev/brianweloba/filter/AuthFilter.java
new file mode 100644
index 0000000..419e2e5
--- /dev/null
+++ b/emmas/src/main/java/dev/brianweloba/filter/AuthFilter.java
@@ -0,0 +1,27 @@
+package dev.brianweloba.filter;
+
+import jakarta.servlet.*;
+import jakarta.servlet.annotation.WebFilter;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+
+import java.io.IOException;
+
+@WebFilter("/events/*")
+public class AuthFilter implements Filter {
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+ HttpSession session = httpRequest.getSession(false);
+
+ if (session == null || session.getAttribute("user") == null) {
+ httpResponse.sendRedirect(httpRequest.getContextPath() + "/login");
+ } else {
+ chain.doFilter(request, response);
+ }
+ }
+}
\ No newline at end of file
diff --git a/emmas/src/main/java/dev/brianweloba/model/Event.java b/emmas/src/main/java/dev/brianweloba/model/Event.java
index 8915a35..e3d7e99 100644
--- a/emmas/src/main/java/dev/brianweloba/model/Event.java
+++ b/emmas/src/main/java/dev/brianweloba/model/Event.java
@@ -2,15 +2,17 @@
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
-import lombok.Data;
+import lombok.*;
import java.util.Date;
+import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@Entity
@Table(name = "events")
-@Data
+@Getter @Setter @ToString(exclude = "rsvps")
+@NoArgsConstructor
public class Event {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -52,8 +54,8 @@ public class Event {
@Column(name = "event_capacity")
private int eventCapacity;
- @OneToMany(mappedBy = "event",fetch = FetchType.LAZY, cascade = CascadeType.ALL)
- private Set rsvps;
+ @OneToMany(mappedBy = "event", cascade = CascadeType.ALL,orphanRemoval = true)
+ private Set rsvps = new HashSet<>();
@Column(nullable = false, unique = true, length = 36, name = "edit_token")
private String editToken;
@@ -64,4 +66,26 @@ public void generateEditToken() {
this.editToken = UUID.randomUUID().toString();
}
}
+
+ public void addRsvp(RSVP rsvp) {
+ rsvps.add(rsvp);
+ rsvp.setEvent(this);
+ }
+
+ public void removeRsvp(RSVP rsvp) {
+ rsvps.remove(rsvp);
+ rsvp.setEvent(null);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Event)) return false;
+ return id != null && id.equals(((Event) o).getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
}
diff --git a/emmas/src/main/java/dev/brianweloba/model/RSVP.java b/emmas/src/main/java/dev/brianweloba/model/RSVP.java
index c579b49..7832f28 100644
--- a/emmas/src/main/java/dev/brianweloba/model/RSVP.java
+++ b/emmas/src/main/java/dev/brianweloba/model/RSVP.java
@@ -2,13 +2,16 @@
import jakarta.persistence.*;
-import lombok.Data;
+import lombok.*;
import java.util.Date;
-@Data
@Entity
@Table(name = "rsvps")
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
public class RSVP {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -18,6 +21,10 @@ public class RSVP {
@JoinColumn(name = "event_id", nullable = false)
private Event event;
+ @ManyToOne
+ @JoinColumn(name = "user_id", nullable = true)
+ private User user;
+
private String name;
private String email;
private int guests;
@@ -26,4 +33,16 @@ public class RSVP {
private Date createdAt;
@Column(name = "updated_at", insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
private Date updatedAt;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof RSVP)) return false;
+ return id != null && id.equals(((RSVP) o).getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
}
diff --git a/emmas/src/main/java/dev/brianweloba/model/User.java b/emmas/src/main/java/dev/brianweloba/model/User.java
new file mode 100644
index 0000000..cbe676d
--- /dev/null
+++ b/emmas/src/main/java/dev/brianweloba/model/User.java
@@ -0,0 +1,63 @@
+package dev.brianweloba.model;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+@Entity
+@Table(name = "users")
+@Getter
+@Setter
+@NoArgsConstructor
+public class User {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List rsvps = new ArrayList<>();
+
+ @NotBlank(message = "Username is required")
+ @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
+ @Column(nullable = false, unique = true)
+ private String username;
+
+ @NotBlank(message = "Password is required")
+ @Size(min = 8, message = "Password must be at least 8 characters long")
+ @Column(nullable = false)
+ private String password;
+
+ @NotBlank(message = "Email is required")
+ @Email(message = "Email should be valid")
+ @Column(nullable = false, unique = true)
+ private String email;
+
+ @Column(name = "created_at", insertable = false, updatable = false,
+ columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
+ private Date createdAt;
+
+ @Column(name = "updated_at", insertable = false, updatable = false,
+ columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
+ private Date updatedAt;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof User)) return false;
+ return id != null && id.equals(((User) o).getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+}
+
diff --git a/emmas/src/main/java/dev/brianweloba/servlet/CreateEvent.java b/emmas/src/main/java/dev/brianweloba/servlet/CreateEvent.java
index b7d3530..2eaabfc 100644
--- a/emmas/src/main/java/dev/brianweloba/servlet/CreateEvent.java
+++ b/emmas/src/main/java/dev/brianweloba/servlet/CreateEvent.java
@@ -2,6 +2,7 @@
import dev.brianweloba.dao.EventDAO;
import dev.brianweloba.model.Event;
+import dev.brianweloba.util.CookieUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;
@@ -39,13 +40,10 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
event = eventDAO.create(event);
session.removeAttribute("event");
-
- Cookie tokenCookie = new Cookie("event_" + event.getId() + "_token", event.getEditToken());
- tokenCookie.setMaxAge(60 * 60 * 24 * 365);
- tokenCookie.setPath("/");
- response.addCookie(tokenCookie);
-
+ CookieUtil.setCookie(response, event);
response.sendRedirect(request.getContextPath() + "/events");
}
+
+
}
diff --git a/emmas/src/main/java/dev/brianweloba/servlet/DeleteEvent.java b/emmas/src/main/java/dev/brianweloba/servlet/DeleteEvent.java
index cb9d9f9..f44f040 100644
--- a/emmas/src/main/java/dev/brianweloba/servlet/DeleteEvent.java
+++ b/emmas/src/main/java/dev/brianweloba/servlet/DeleteEvent.java
@@ -1,4 +1,63 @@
package dev.brianweloba.servlet;
-public class DeleteEvent {
-}
+import dev.brianweloba.dao.EventDAO;
+import dev.brianweloba.model.Event;
+import dev.brianweloba.util.CookieUtil;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+
+@WebServlet("/events/delete")
+public class DeleteEvent extends HttpServlet {
+ private final EventDAO eventDAO = new EventDAO();
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+ try {
+ String idParam = request.getParameter("id");
+ if (idParam == null || idParam.trim().isEmpty()) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Event ID is required");
+ return;
+ }
+
+ Long eventId = Long.parseLong(idParam);
+
+ String token = CookieUtil.getEditTokenFromCookies(request, eventId);
+ if (token == null) {
+ request.setAttribute("error", "Invalid token. You don't have permission to delete this event.");
+ request.getRequestDispatcher("/WEB-INF/views/error.jsp").forward(request, response);
+ return;
+ }
+
+ Event event = eventDAO.findByIdAndToken(eventId, token);
+ if (event == null) {
+ request.setAttribute("error", "Event not found or you don't have permission to delete this event.");
+ request.getRequestDispatcher("/WEB-INF/views/error.jsp").forward(request, response);
+ return;
+ }
+
+ boolean success = eventDAO.delete(eventId, token);
+
+ if (success) {
+ request.getSession().setAttribute("statusMessage", "Event deleted successfully!");
+ response.sendRedirect(request.getContextPath() + "/events");
+ } else {
+ request.setAttribute("error", "Failed to delete event.");
+ request.getRequestDispatcher("/WEB-INF/views/error.jsp").forward(request, response);
+ }
+
+ } catch (NumberFormatException e) {
+ request.setAttribute("error", "Invalid event ID format");
+ request.getRequestDispatcher("/WEB-INF/views/error.jsp").forward(request, response);
+ } catch (Exception e) {
+ request.setAttribute("error", "Error deleting event: " + e.getMessage());
+ request.getRequestDispatcher("/WEB-INF/views/error.jsp").forward(request, response);
+ }
+ }
+}
\ No newline at end of file
diff --git a/emmas/src/main/java/dev/brianweloba/servlet/EventServlet.java b/emmas/src/main/java/dev/brianweloba/servlet/EventServlet.java
index ac6e0e2..464b33e 100644
--- a/emmas/src/main/java/dev/brianweloba/servlet/EventServlet.java
+++ b/emmas/src/main/java/dev/brianweloba/servlet/EventServlet.java
@@ -2,6 +2,7 @@
import dev.brianweloba.dao.EventDAO;
import dev.brianweloba.model.Event;
+import dev.brianweloba.util.CookieUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
@@ -9,7 +10,10 @@
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+
@WebServlet("/events")
public class EventServlet extends HttpServlet {
@@ -38,6 +42,15 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
List events = eventDAO.findPaginated(startIndex, pageSize);
+ Map editTokens = new HashMap<>();
+ for (Event event : events) {
+ String token = CookieUtil.getEditTokenFromCookies(request, event.getId());
+ if (token != null) {
+ editTokens.put(event.getId(), token);
+ }
+ }
+ request.setAttribute("editTokens", editTokens);
+
request.setAttribute("events", events);
request.setAttribute("currentPage", currentPage);
request.setAttribute("pageSize", pageSize);
diff --git a/emmas/src/main/java/dev/brianweloba/servlet/IndexServlet.java b/emmas/src/main/java/dev/brianweloba/servlet/IndexServlet.java
index 18cba42..0af7713 100644
--- a/emmas/src/main/java/dev/brianweloba/servlet/IndexServlet.java
+++ b/emmas/src/main/java/dev/brianweloba/servlet/IndexServlet.java
@@ -7,11 +7,15 @@
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
-@WebServlet("/index")
+@WebServlet( "/index")
public class IndexServlet
extends HttpServlet
{
@@ -20,8 +24,20 @@ public class IndexServlet
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
- List events = eventDAO.findAll();
- request.setAttribute("events", events);
- request.getRequestDispatcher("/WEB-INF/views/index.jsp").forward(request, response);
+ HttpSession session = request.getSession(false);
+ if (session != null && session.getAttribute("user") != null) {
+ List events = eventDAO.findAll();
+
+ Set eventTypes = events.stream()
+ .map(Event::getEventType)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
+ request.setAttribute("events", events);
+ request.setAttribute("eventTypes", eventTypes);
+ request.getRequestDispatcher("/WEB-INF/views/index.jsp").forward(request, response);
+ } else {
+ response.sendRedirect(request.getContextPath() + "/login");
+ }
}
}
diff --git a/emmas/src/main/java/dev/brianweloba/servlet/LoginServlet.java b/emmas/src/main/java/dev/brianweloba/servlet/LoginServlet.java
new file mode 100644
index 0000000..59552a6
--- /dev/null
+++ b/emmas/src/main/java/dev/brianweloba/servlet/LoginServlet.java
@@ -0,0 +1,43 @@
+package dev.brianweloba.servlet;
+
+import dev.brianweloba.dao.UserDAO;
+import dev.brianweloba.model.User;
+import dev.brianweloba.util.PasswordUtil;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+
+import java.io.IOException;
+
+@WebServlet("/login")
+public class LoginServlet extends HttpServlet {
+ private final UserDAO userDAO = new UserDAO();
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ request.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(request, response);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String username = request.getParameter("username");
+ String password = request.getParameter("password");
+
+ User user = userDAO.findByUsername(username);
+
+ if (user != null && PasswordUtil.checkPassword(password, user.getPassword())) {
+ HttpSession session = request.getSession();
+ session.setAttribute("user", user);
+
+ response.sendRedirect(request.getContextPath() + "/index");
+ } else {
+ request.setAttribute("error", "Invalid username or password");
+ request.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(request, response);
+ }
+ }
+}
\ No newline at end of file
diff --git a/emmas/src/main/java/dev/brianweloba/servlet/LogoutServlet.java b/emmas/src/main/java/dev/brianweloba/servlet/LogoutServlet.java
new file mode 100644
index 0000000..50c49ec
--- /dev/null
+++ b/emmas/src/main/java/dev/brianweloba/servlet/LogoutServlet.java
@@ -0,0 +1,23 @@
+package dev.brianweloba.servlet;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+
+import java.io.IOException;
+
+@WebServlet("/logout")
+public class LogoutServlet extends HttpServlet {
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ HttpSession session = request.getSession(false);
+ if (session != null) {
+ session.invalidate();
+ }
+ response.sendRedirect(request.getContextPath() + "/index");
+ }
+}
\ No newline at end of file
diff --git a/emmas/src/main/java/dev/brianweloba/servlet/RSVPServlet.java b/emmas/src/main/java/dev/brianweloba/servlet/RSVPServlet.java
index ccf4678..401f9c9 100644
--- a/emmas/src/main/java/dev/brianweloba/servlet/RSVPServlet.java
+++ b/emmas/src/main/java/dev/brianweloba/servlet/RSVPServlet.java
@@ -1,19 +1,62 @@
package dev.brianweloba.servlet;
import com.google.gson.JsonObject;
+import dev.brianweloba.dao.EventDAO;
import dev.brianweloba.dao.RsvpDAO;
import dev.brianweloba.model.RSVP;
+import dev.brianweloba.model.User;
+import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
import java.io.IOException;
+import java.util.List;
@WebServlet("/events/rsvp")
public class RSVPServlet extends HttpServlet {
private final RsvpDAO rsvpDAO = new RsvpDAO();
+ private final EventDAO eventDAO = new EventDAO();
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {
+ try{
+ String eventIdParam = request.getParameter("eventId");
+ if(eventIdParam == null || eventIdParam.trim().isEmpty()){
+ request.setAttribute("error", "Event ID is required");
+ request.getRequestDispatcher("/WEB-INF/views/error.jsp").forward(request, response);
+ return;
+ }
+ long eventId;
+ try {
+ eventId = Long.parseLong(eventIdParam);
+ } catch (NumberFormatException e) {
+ request.setAttribute("error", "Invalid Event ID format");
+ request.getRequestDispatcher("/WEB-INF/views/error.jsp").forward(request, response);
+ return;
+ }
+
+ List attendees = rsvpDAO.getEventAttendees(eventId);
+
+ int totalGuests = 0;
+ for (RSVP rsvp : attendees) {
+ totalGuests += rsvp.getGuests();
+ }
+
+ request.setAttribute("attendees", attendees);
+ request.setAttribute("totalGuests", totalGuests);
+
+ request.getRequestDispatcher("/WEB-INF/views/attendees.jsp").forward(request, response);
+
+ } catch (Exception e) {
+ request.setAttribute("error", "Error fetching attendees: " + e.getMessage());
+ request.getRequestDispatcher("/WEB-INF/views/error.jsp").forward(request, response);
+ }
+
+
+ }
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("application/json");
@@ -21,7 +64,6 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
try {
RSVP rsvp = new RSVP();
-
rsvp.setName(request.getParameter("name"));
rsvp.setEmail(request.getParameter("email"));
rsvp.setGuests(Integer.parseInt(request.getParameter("guests")));
@@ -36,6 +78,12 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
throw new IllegalArgumentException("Guests must be a positive number");
}
+ HttpSession session = request.getSession();
+ User user = (User) session.getAttribute("user");
+ if (user != null) {
+ rsvp.setUser(user);
+ }
+
rsvpDAO.create(rsvp, Long.parseLong(request.getParameter("eventId")));
jsonResponse.addProperty("success", true);
diff --git a/emmas/src/main/java/dev/brianweloba/servlet/RegistrationServlet.java b/emmas/src/main/java/dev/brianweloba/servlet/RegistrationServlet.java
new file mode 100644
index 0000000..001bd82
--- /dev/null
+++ b/emmas/src/main/java/dev/brianweloba/servlet/RegistrationServlet.java
@@ -0,0 +1,59 @@
+package dev.brianweloba.servlet;
+
+import dev.brianweloba.dao.UserDAO;
+import dev.brianweloba.model.User;
+import dev.brianweloba.util.PasswordUtil;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+
+@WebServlet("/register")
+public class RegistrationServlet extends HttpServlet {
+ private final UserDAO userDAO = new UserDAO();
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ request.getRequestDispatcher("/WEB-INF/views/register.jsp").forward(request, response);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String username = request.getParameter("username");
+ String email = request.getParameter("email");
+ String password = request.getParameter("password");
+ String confirmPassword = request.getParameter("confirmPassword");
+
+ if (!password.equals(confirmPassword)) {
+ request.setAttribute("error", "Passwords do not match");
+ request.getRequestDispatcher("/WEB-INF/views/register.jsp").forward(request, response);
+ return;
+ }
+
+ if (userDAO.findByUsername(username) != null) {
+ request.setAttribute("error", "Username already taken");
+ request.getRequestDispatcher("/WEB-INF/views/register.jsp").forward(request, response);
+ return;
+ }
+
+ if (userDAO.findByEmail(email) != null) {
+ request.setAttribute("error", "Email already registered");
+ request.getRequestDispatcher("/WEB-INF/views/register.jsp").forward(request, response);
+ return;
+ }
+
+ User user = new User();
+ user.setUsername(username);
+ user.setEmail(email);
+ user.setPassword(PasswordUtil.hashPassword(password));
+
+ userDAO.save(user);
+
+ response.sendRedirect(request.getContextPath() + "/login");
+ }
+}
\ No newline at end of file
diff --git a/emmas/src/main/java/dev/brianweloba/servlet/SearchServlet.java b/emmas/src/main/java/dev/brianweloba/servlet/SearchServlet.java
index fe8426a..4468c5a 100644
--- a/emmas/src/main/java/dev/brianweloba/servlet/SearchServlet.java
+++ b/emmas/src/main/java/dev/brianweloba/servlet/SearchServlet.java
@@ -23,17 +23,16 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t
System.out.println("query = " + query);
if(query !=null && !query.trim().isEmpty()){
events = eventDAO.search(query.trim());
-
- }else {
+ } else {
events = eventDAO.findAll();
- request.setAttribute("events", events);
}
+ System.out.println("EVENT:"+events.getFirst());
+ System.out.println("RSVPS:"+events.getFirst().getRsvps());
+
request.setAttribute("events", events);
request.setAttribute("searchQuery", query);
request.getRequestDispatcher("/WEB-INF/views/eventsBody.jsp").forward(request, response);
}
-
-
}
diff --git a/emmas/src/main/java/dev/brianweloba/servlet/UpdateEvent.java b/emmas/src/main/java/dev/brianweloba/servlet/UpdateEvent.java
index d173657..cba5afe 100644
--- a/emmas/src/main/java/dev/brianweloba/servlet/UpdateEvent.java
+++ b/emmas/src/main/java/dev/brianweloba/servlet/UpdateEvent.java
@@ -3,10 +3,10 @@
import dev.brianweloba.dao.EventDAO;
import dev.brianweloba.model.Event;
import dev.brianweloba.services.EventService;
+import dev.brianweloba.util.CookieUtil;
import dev.brianweloba.util.ValidationUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
-import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -22,7 +22,7 @@ public class UpdateEvent extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Long eventId = Long.parseLong(request.getParameter("id"));
- String token = getEditTokenFromCookies(request, eventId);
+ String token = CookieUtil.getEditTokenFromCookies(request, eventId);
Event event = getEvent(request, response, token, eventId);
if (event == null) return;
@@ -50,7 +50,7 @@ private Event getEvent(HttpServletRequest request, HttpServletResponse response,
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
Long eventId = Long.parseLong(request.getParameter("eventId"));
- String token = getEditTokenFromCookies(request, eventId);
+ String token = CookieUtil.getEditTokenFromCookies(request, eventId);
Event event = getEvent(request, response, token, eventId);
if (event == null) return;
@@ -59,26 +59,15 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
if(ValidationUtil.validateEvent(request,response,event,"/WEB-INF/views/update.jsp")) return;
+ event.setEventType(request.getParameter("eventType").toUpperCase());
+
+ if(event.getEventType().isEmpty()){
+ request.setAttribute("eventTypeError", "Event Type is empty");
+ request.getRequestDispatcher("/WEB-INF/views/prediction.jsp").forward(request, response);
+ return;
+ }
eventDAO.update(event, token);
response.sendRedirect(request.getContextPath() + "/events");
}
-
- private String getEditTokenFromCookies(HttpServletRequest request, Long eventId) {
- Cookie[] cookies = request.getCookies();
- if (cookies != null) {
- for (Cookie cookie : cookies) {
- System.out.println("Cookie Name: " + cookie.getName() + ", Value: " + cookie.getValue());
-
- if (cookie.getName().equals("event_" + eventId + "_token")) {
- System.out.println("Cookie Found: " + cookie.getName() + ", Value: " + cookie.getValue());
-
- return cookie.getValue();
- }
-
- }
-
- }
- return null;
- }
}
diff --git a/emmas/src/main/java/dev/brianweloba/util/CookieUtil.java b/emmas/src/main/java/dev/brianweloba/util/CookieUtil.java
new file mode 100644
index 0000000..f58235f
--- /dev/null
+++ b/emmas/src/main/java/dev/brianweloba/util/CookieUtil.java
@@ -0,0 +1,29 @@
+package dev.brianweloba.util;
+
+import dev.brianweloba.model.Event;
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+public class CookieUtil {
+ public static void setCookie(HttpServletResponse response, Event event) {
+ Cookie tokenCookie = new Cookie("event_" + event.getId() + "_token", event.getEditToken());
+ tokenCookie.setMaxAge(60 * 60 * 24 * 365);
+ tokenCookie.setPath("/");
+ response.addCookie(tokenCookie);
+ }
+
+ public static String getEditTokenFromCookies(HttpServletRequest request, Long eventId) {
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null) {
+ for (Cookie cookie : cookies) {
+ if (cookie.getName().equals("event_" + eventId + "_token")) {
+ return cookie.getValue();
+ }
+
+ }
+
+ }
+ return null;
+ }
+}
diff --git a/emmas/src/main/java/dev/brianweloba/util/PasswordUtil.java b/emmas/src/main/java/dev/brianweloba/util/PasswordUtil.java
new file mode 100644
index 0000000..51879bc
--- /dev/null
+++ b/emmas/src/main/java/dev/brianweloba/util/PasswordUtil.java
@@ -0,0 +1,14 @@
+package dev.brianweloba.util;
+
+
+import org.mindrot.jbcrypt.BCrypt;
+
+public class PasswordUtil {
+ public static String hashPassword(String plainTextPassword) {
+ return BCrypt.hashpw(plainTextPassword, BCrypt.gensalt());
+ }
+
+ public static boolean checkPassword(String plainTextPassword, String hashedPassword) {
+ return BCrypt.checkpw(plainTextPassword, hashedPassword);
+ }
+}
\ No newline at end of file
diff --git a/emmas/src/main/java/dev/brianweloba/weka/ClassifierLogger.java b/emmas/src/main/java/dev/brianweloba/weka/ClassifierLogger.java
new file mode 100644
index 0000000..ff9a402
--- /dev/null
+++ b/emmas/src/main/java/dev/brianweloba/weka/ClassifierLogger.java
@@ -0,0 +1,48 @@
+package dev.brianweloba.weka;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class ClassifierLogger {
+ private static final String LOG_FILE = "classifier_results.log";
+ private static final String METRICS_FILE = "classifier_metrics.csv";
+ private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ // Initialize metrics file with headers if it doesn't exist
+ static {
+ try (PrintWriter writer = new PrintWriter(new FileWriter(METRICS_FILE, true))) {
+ if (new File(METRICS_FILE).length() == 0) {
+ writer.println("timestamp,accuracy,precision,recall,f1,training_size,test_size");
+ }
+ } catch (IOException e) {
+ System.err.println("Error initializing metrics file: " + e.getMessage());
+ }
+ }
+
+ public static void log(String message) {
+ try (PrintWriter writer = new PrintWriter(new FileWriter(LOG_FILE, true))) {
+ String timestamp = DATE_FORMAT.format(new Date());
+ writer.printf("[%s] %s%n", timestamp, message);
+ System.out.printf("[%s] %s%n", timestamp, message); // Also print to console
+ } catch (IOException e) {
+ System.err.println("Error writing to log file: " + e.getMessage());
+ }
+ }
+
+ public static void logMetrics(double accuracy, double precision,
+ double recall, double f1,
+ int trainingSize, int testSize) {
+ try (PrintWriter writer = new PrintWriter(new FileWriter(METRICS_FILE, true))) {
+ String timestamp = DATE_FORMAT.format(new Date());
+ writer.printf("%s,%.4f,%.4f,%.4f,%.4f,%d,%d%n",
+ timestamp, accuracy, precision,
+ recall, f1, trainingSize, testSize);
+ } catch (IOException e) {
+ System.err.println("Error writing metrics: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/emmas/src/main/java/dev/brianweloba/weka/EventClassifierTester.java b/emmas/src/main/java/dev/brianweloba/weka/EventClassifierTester.java
new file mode 100644
index 0000000..00162af
--- /dev/null
+++ b/emmas/src/main/java/dev/brianweloba/weka/EventClassifierTester.java
@@ -0,0 +1,351 @@
+package dev.brianweloba.weka;
+
+import dev.brianweloba.model.Event;
+import weka.classifiers.Evaluation;
+import weka.core.Instances;
+import weka.core.converters.ArffSaver;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.*;
+
+public class EventClassifierTester {
+ private final EventTypeClassifier classifier;
+ private final String outputDir;
+ private static final boolean VERBOSE_LOGGING = true; // Toggle for detailed logs
+
+ public EventClassifierTester(String outputDir) {
+ this.classifier = new EventTypeClassifier();
+ this.outputDir = outputDir;
+ new File(outputDir).mkdirs(); // Ensure directory exists
+ }
+
+ public void runFullTest(List allEvents) throws Exception {
+ ClassifierLogger.log("Starting event classification evaluation");
+
+ // Ensure we have enough data
+ if (allEvents.size() < 50) {
+ ClassifierLogger.log("Warning: Small dataset size (" + allEvents.size() + " events)");
+ }
+
+ // Quick summary of class distribution
+ logClassDistributionSummary(allEvents);
+
+ // Split data (70% train, 30% test)
+ Collections.shuffle(allEvents, new Random(42)); // Fixed seed for reproducibility
+ int splitPoint = (int) (allEvents.size() * 0.7);
+ List trainingEvents = allEvents.subList(0, splitPoint);
+ List testEvents = allEvents.subList(splitPoint, allEvents.size());
+
+ // Train model
+ ClassifierLogger.log("Training with " + trainingEvents.size() + " events");
+ classifier.trainModel(trainingEvents);
+ saveModelAndData(trainingEvents, "training");
+
+ // Test and save results
+ ClassifierLogger.log("Testing with " + testEvents.size() + " events");
+ TestResults results = evaluateOnTestSet(testEvents);
+
+ // Calculate per-class metrics
+ Map classMetrics = calculatePerClassMetrics(testEvents);
+
+ // Save detailed results
+ saveTestResults(results, testEvents, classMetrics);
+
+ // Log summary metrics
+ logSummaryMetrics(results);
+
+ // Perform cross-validation
+ performCrossValidation(allEvents, 5);
+ }
+
+ private void logClassDistributionSummary(List events) {
+ Map typeCounts = new HashMap<>();
+ for (Event event : events) {
+ if (event.getEventType() != null) {
+ typeCounts.merge(event.getEventType(), 1, Integer::sum);
+ }
+ }
+
+ ClassifierLogger.log("Class distribution: " +
+ typeCounts.entrySet().stream()
+ .sorted(Map.Entry.comparingByValue().reversed())
+ .map(e -> String.format("%s=%d(%.1f%%)",
+ e.getKey(), e.getValue(), (double)e.getValue()*100/events.size()))
+ .reduce((a, b) -> a + ", " + b)
+ .orElse(""));
+ }
+
+ private void logSummaryMetrics(TestResults results) {
+ ClassifierLogger.log(String.format(
+ "Results: Accuracy=%.1f%%, Precision=%.3f, Recall=%.3f, F1=%.3f",
+ results.accuracy * 100, results.precision, results.recall, results.f1
+ ));
+ }
+
+ private void performCrossValidation(List allEvents, int folds) {
+ try {
+ Random rand = new Random(42);
+ Collections.shuffle(allEvents, rand);
+
+ int foldSize = allEvents.size() / folds;
+ double totalAccuracy = 0;
+ double[] foldAccuracies = new double[folds];
+
+ for (int i = 0; i < folds; i++) {
+ // Split for this fold
+ List cvTest = new ArrayList<>(allEvents.subList(i * foldSize,
+ Math.min((i + 1) * foldSize, allEvents.size())));
+ List cvTrain = new ArrayList<>(allEvents);
+ cvTrain.removeAll(cvTest);
+
+ // Train
+ EventTypeClassifier cvClassifier = new EventTypeClassifier();
+ cvClassifier.trainModel(cvTrain);
+
+ // Test
+ int correct = 0;
+ for (Event event : cvTest) {
+ String predicted = cvClassifier.predictEventType(event);
+ if (predicted.equals(event.getEventType())) {
+ correct++;
+ }
+ }
+
+ foldAccuracies[i] = (double) correct / cvTest.size();
+ totalAccuracy += foldAccuracies[i];
+
+ if (VERBOSE_LOGGING) {
+ ClassifierLogger.log(String.format("Fold %d accuracy: %.1f%%",
+ i+1, foldAccuracies[i] * 100));
+ }
+ }
+
+ // Report the summary with min/max/avg
+ double avgAccuracy = totalAccuracy / folds;
+ double minAccuracy = Arrays.stream(foldAccuracies).min().orElse(0);
+ double maxAccuracy = Arrays.stream(foldAccuracies).max().orElse(0);
+
+ ClassifierLogger.log(String.format("CV results: Avg=%.1f%%, Min=%.1f%%, Max=%.1f%% (%d folds)",
+ avgAccuracy * 100, minAccuracy * 100, maxAccuracy * 100, folds));
+
+ } catch (Exception e) {
+ ClassifierLogger.log("CV error: " + e.getMessage());
+ }
+ }
+
+ private void saveConfusionMatrix(Evaluation eval, Instances data) {
+ try (PrintWriter writer = new PrintWriter(new File(outputDir + "/confusion_matrix.txt"))) {
+ writer.println("=== Confusion Matrix ===");
+ writer.println(eval.toMatrixString());
+ writer.println("\n=== Detailed Accuracy By Class ===");
+ writer.println(eval.toClassDetailsString());
+ } catch (Exception e) {
+ ClassifierLogger.log("Error saving confusion matrix: " + e.getMessage());
+ }
+ }
+
+ private Map calculatePerClassMetrics(List testEvents) {
+ Map metrics = new HashMap<>();
+ Map truePositives = new HashMap<>();
+ Map falsePositives = new HashMap<>();
+ Map falseNegatives = new HashMap<>();
+ Map totalActual = new HashMap<>();
+
+ // Count actual class distribution
+ for (Event event : testEvents) {
+ String actual = event.getEventType();
+ totalActual.merge(actual, 1, Integer::sum);
+ }
+
+ // Calculate metrics
+ for (Event event : testEvents) {
+ String actual = event.getEventType();
+ String predicted = classifier.predictEventType(event);
+
+ if (actual.equals(predicted)) {
+ truePositives.merge(actual, 1, Integer::sum);
+ } else {
+ falseNegatives.merge(actual, 1, Integer::sum);
+ falsePositives.merge(predicted, 1, Integer::sum);
+ }
+ }
+
+ // Calculate precision, recall, and F1 for each class
+ for (String className : totalActual.keySet()) {
+ int tp = truePositives.getOrDefault(className, 0);
+ int fp = falsePositives.getOrDefault(className, 0);
+ int fn = falseNegatives.getOrDefault(className, 0);
+ int total = totalActual.get(className);
+
+ double precision = (tp + fp == 0) ? 0 : (double) tp / (tp + fp);
+ double recall = (double) tp / total;
+ double f1 = (precision + recall == 0) ? 0 : 2 * precision * recall / (precision + recall);
+
+ metrics.put(className, new ClassMetrics(precision, recall, f1, total));
+ }
+
+ return metrics;
+ }
+
+ private TestResults evaluateOnTestSet(List testEvents) {
+ TestResults results = new TestResults();
+ int correct = 0;
+ Map classCounts = new HashMap<>();
+ Map correctCounts = new HashMap<>();
+ List misclassifications = new ArrayList<>();
+
+ try (PrintWriter writer = new PrintWriter(new File(outputDir + "/detailed_results.csv"))) {
+ writer.println("actual,predicted,confidence,title,is_correct");
+
+ for (Event event : testEvents) {
+ String actual = event.getEventType();
+ String predicted = classifier.predictEventType(event);
+ Map probs = classifier.getPredictionProbabilities(event);
+ double confidence = probs.getOrDefault(predicted, 0.0);
+ boolean isCorrect = actual.equals(predicted);
+
+ writer.printf("%s,%s,%.4f,%s,%b%n",
+ actual, predicted, confidence,
+ event.getTitle().replace(",", ""), isCorrect);
+
+ // Update class statistics
+ classCounts.merge(actual, 1, Integer::sum);
+ if (isCorrect) {
+ correct++;
+ correctCounts.merge(actual, 1, Integer::sum);
+ } else {
+ // Store misclassifications instead of logging each one
+ misclassifications.add(String.format(
+ "Actual=%s, Predicted=%s (%.0f%%) for '%s'",
+ actual, predicted, confidence*100, event.getTitle()));
+ }
+ }
+
+ // Calculate global metrics
+ results.accuracy = (double) correct / testEvents.size();
+
+ // Calculate confusion matrix-based metrics
+ double totalPrecision = 0;
+ double totalRecall = 0;
+ int classCount = classCounts.size();
+
+ for (String className : classCounts.keySet()) {
+ int truePositives = correctCounts.getOrDefault(className, 0);
+ int total = classCounts.get(className);
+
+ // Calculate predicted count (for precision)
+ long predictedCount = testEvents.stream()
+ .filter(e -> classifier.predictEventType(e).equals(className))
+ .count();
+
+ double precision = predictedCount == 0 ? 0 : (double) truePositives / predictedCount;
+ double recall = (double) truePositives / total;
+
+ totalPrecision += precision;
+ totalRecall += recall;
+ }
+
+ // Calculate macro-averaged metrics
+ results.precision = totalPrecision / classCount;
+ results.recall = totalRecall / classCount;
+ results.f1 = (results.precision + results.recall == 0) ? 0 :
+ 2 * results.precision * results.recall / (results.precision + results.recall);
+
+ // Log misclassification summary
+ int misclassificationCount = misclassifications.size();
+ if (misclassificationCount > 0) {
+ ClassifierLogger.log(String.format("Misclassifications: %d/%d (%.1f%%)",
+ misclassificationCount, testEvents.size(),
+ (double)misclassificationCount*100/testEvents.size()));
+
+ // Only log the first few misclassifications if verbose
+ if (VERBOSE_LOGGING) {
+ int limit = Math.min(5, misclassificationCount);
+ for (int i = 0; i < limit; i++) {
+ ClassifierLogger.log("MISCLASSIFIED: " + misclassifications.get(i));
+ }
+ if (misclassificationCount > limit) {
+ ClassifierLogger.log("... and " + (misclassificationCount - limit) + " more");
+ }
+ }
+ }
+
+ } catch (Exception e) {
+ ClassifierLogger.log("Error during testing: " + e.getMessage());
+ }
+
+ return results;
+ }
+
+ private void saveModelAndData(List events, String prefix) throws Exception {
+ // Save ARFF file for inspection
+ Instances data = classifier.getTrainingData();
+ ArffSaver saver = new ArffSaver();
+ saver.setInstances(data);
+ saver.setFile(new File(outputDir + "/" + prefix + "_data.arff"));
+ saver.writeBatch();
+
+ // Save model
+ classifier.saveModel(outputDir + "/" + prefix + "_model.model");
+ }
+
+ private void saveTestResults(TestResults results, List testEvents,
+ Map classMetrics) {
+ try (PrintWriter writer = new PrintWriter(new File(outputDir + "/summary_results.txt"))) {
+ writer.println("=== Classifier Test Results ===");
+ writer.printf("Accuracy: %.2f%%%n", results.accuracy * 100);
+ writer.printf("Macro-Averaged Precision: %.4f%n", results.precision);
+ writer.printf("Macro-Averaged Recall: %.4f%n", results.recall);
+ writer.printf("Macro-Averaged F1 Score: %.4f%n", results.f1);
+
+ writer.println("\n=== Per-Class Performance ===");
+ for (Map.Entry entry : classMetrics.entrySet()) {
+ ClassMetrics metrics = entry.getValue();
+ writer.printf("%s (n=%d):%n", entry.getKey(), metrics.count);
+ writer.printf(" Precision: %.4f%n", metrics.precision);
+ writer.printf(" Recall: %.4f%n", metrics.recall);
+ writer.printf(" F1 Score: %.4f%n", metrics.f1);
+ }
+
+ writer.println("\n=== Test Set Distribution ===");
+
+ // Count actual class distribution
+ Map actualCounts = new HashMap<>();
+ for (Event e : testEvents) {
+ actualCounts.merge(e.getEventType(), 1, Integer::sum);
+ }
+
+ for (Map.Entry entry : actualCounts.entrySet()) {
+ writer.printf("%s: %d (%.1f%%)%n",
+ entry.getKey(), entry.getValue(),
+ (double) entry.getValue() / testEvents.size() * 100);
+ }
+
+ } catch (IOException e) {
+ ClassifierLogger.log("Error saving test results: " + e.getMessage());
+ }
+ }
+
+ private static class TestResults {
+ double accuracy;
+ double precision;
+ double recall;
+ double f1;
+ }
+
+ private static class ClassMetrics {
+ double precision;
+ double recall;
+ double f1;
+ int count;
+
+ public ClassMetrics(double precision, double recall, double f1, int count) {
+ this.precision = precision;
+ this.recall = recall;
+ this.f1 = f1;
+ this.count = count;
+ }
+ }
+}
\ No newline at end of file
diff --git a/emmas/src/main/java/dev/brianweloba/weka/EventTypeClassifier.java b/emmas/src/main/java/dev/brianweloba/weka/EventTypeClassifier.java
index 4640ef0..26435cd 100644
--- a/emmas/src/main/java/dev/brianweloba/weka/EventTypeClassifier.java
+++ b/emmas/src/main/java/dev/brianweloba/weka/EventTypeClassifier.java
@@ -1,136 +1,442 @@
package dev.brianweloba.weka;
import dev.brianweloba.model.Event;
-import weka.classifiers.trees.RandomForest;
-import weka.core.Attribute;
-import weka.core.DenseInstance;
-import weka.core.Instance;
-import weka.core.Instances;
+import weka.classifiers.bayes.NaiveBayes;
+import weka.classifiers.evaluation.Evaluation;
+import weka.classifiers.meta.AdaBoostM1;
+import weka.core.*;
+import weka.core.stopwords.Rainbow;
+import weka.core.tokenizers.NGramTokenizer;
import weka.filters.Filter;
+import weka.filters.supervised.instance.SMOTE;
import weka.filters.unsupervised.attribute.StringToWordVector;
+import weka.classifiers.meta.CVParameterSelection;
import java.util.*;
+import java.util.stream.Collectors;
public class EventTypeClassifier {
- private RandomForest classifier;
+ private static final int MIN_CLASSES_FOR_CLASSIFICATION = 2;
+ private static final int MIN_INSTANCES_FOR_BALANCING = 6;
+ private static final int SMOTE_NEIGHBORS = 5;
+ private static final int SMOTE_PERCENTAGE = 100;
+ private static final int NGRAM_MIN_SIZE = 1;
+ private static final int NGRAM_MAX_SIZE = 2;
+ private static final int WORDS_TO_KEEP = 500;
+ private static final int RANDOM_SEED = 42;
+
+ // private NaiveBayes classifier;
+ private AdaBoostM1 classifier;
private Instances trainingHeader;
public void trainModel(List events) {
- try{
- ArrayList attributes = createAttributes();
-
- Set uniqueEventTypes = new HashSet<>();
- for(Event event: events){
- if(event.getEventType() != null){
- uniqueEventTypes.add(event.getEventType());
- }
- }
+ try {
+ List validEvents = validateAndFilterEvents(events);
+ logClassDistribution(validEvents);
+
+ Instances trainingData = createTrainingDataset(validEvents);
+ Instances filteredData = applyTextProcessingFilters(trainingData);
+ Instances balancedData = balanceDataset(filteredData);
+
+ trainClassifier(balancedData);
+ evaluateModel(balancedData);
+
+ // Add this after model training:
+ ClassifierLogger.log("\n=== AdaBoost Configuration ===");
+ ClassifierLogger.log("Iterations: " + classifier.getNumIterations());
+ ClassifierLogger.log("Using classifier: " + classifier.getClassifier().getClass().getSimpleName());
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to train model: " + e.getMessage(), e);
+ }
+ }
+
+ private List validateAndFilterEvents(List events) {
+ List validEvents = events.stream()
+ .filter(e -> e.getEventType() != null && !e.getEventType().isEmpty())
+ .toList();
+
+ if (validEvents.isEmpty()) {
+ throw new IllegalArgumentException("No valid events with eventType available for training");
+ }
+
+ ClassifierLogger.log("Starting training with " + validEvents.size() + " events");
+
+ Set uniqueEventTypes = validEvents.stream()
+ .map(Event::getEventType)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
+ if (uniqueEventTypes.size() < MIN_CLASSES_FOR_CLASSIFICATION) {
+ throw new IllegalArgumentException("Need at least " + MIN_CLASSES_FOR_CLASSIFICATION +
+ " different event types for classification");
+ }
+
+ return validEvents;
+ }
+
+ private void logClassDistribution(List events) {
+ Map classCounts = events.stream()
+ .collect(Collectors.groupingBy(Event::getEventType, Collectors.counting()));
+
+ ClassifierLogger.log("Class distribution in training data:");
+ classCounts.forEach((type, count) ->
+ ClassifierLogger.log(String.format(" %s: %d (%.1f%%)",
+ type, count, (double) count * 100 / events.size())));
+ }
+
+ private Instances createTrainingDataset(List events) {
+ Set uniqueEventTypes = events.stream()
+ .map(Event::getEventType)
+ .collect(Collectors.toSet());
+
+ ArrayList attributes = createAttributes();
+ Attribute classAttribute = new Attribute("class_event_type", new ArrayList<>(uniqueEventTypes));
+ attributes.add(classAttribute);
+
+ Instances trainingData = new Instances("EventData", attributes, 0);
+ trainingData.setClassIndex(trainingData.numAttributes() - 1);
+
+ for (Event event : events) {
+ addInstance(trainingData, event);
+ }
+
+ return trainingData;
+ }
+
+ private Instances applyTextProcessingFilters(Instances data) throws Exception {
+ int classIndex = data.classIndex();
+ Attribute classAttr = data.attribute(classIndex);
+
+ Instances dataForFiltering = new Instances(data);
+ dataForFiltering.setClassIndex(-1);
+ dataForFiltering.deleteAttributeAt(classIndex);
+
+ StringToWordVector filter = createTextFilter();
+ filter.setInputFormat(dataForFiltering);
+ Instances filteredData = Filter.useFilter(dataForFiltering, filter);
+
+ filteredData.insertAttributeAt(classAttr, filteredData.numAttributes());
+ filteredData.setClassIndex(filteredData.numAttributes() - 1);
+
+ for (int i = 0; i < data.numInstances(); i++) {
+ filteredData.instance(i).setValue(filteredData.classIndex(),
+ data.instance(i).value(classIndex));
+ }
+
+ return filteredData;
+ }
+
+ private StringToWordVector createTextFilter() {
+ StringToWordVector filter = new StringToWordVector();
+ filter.setAttributeIndices("1,2,3,4"); // Text attributes
+ filter.setWordsToKeep(WORDS_TO_KEEP);
+ filter.setTFTransform(true);
+ filter.setIDFTransform(true);
+
+ NGramTokenizer tokenizer = new NGramTokenizer();
+ tokenizer.setNGramMinSize(NGRAM_MIN_SIZE);
+ tokenizer.setNGramMaxSize(NGRAM_MAX_SIZE);
+ filter.setTokenizer(tokenizer);
+
+ filter.setStopwordsHandler(new Rainbow());
+ filter.setStemmer(new weka.core.stemmers.LovinsStemmer());
+
+ return filter;
+ }
+
+ private Instances balanceDataset(Instances data) throws Exception {
+ Set uniqueClasses = new HashSet<>();
+ for (int i = 0; i < data.numInstances(); i++) {
+ uniqueClasses.add(data.instance(i).classValue());
+ }
+
+ if (data.numInstances() < uniqueClasses.size() * MIN_INSTANCES_FOR_BALANCING) {
+ ClassifierLogger.log("Not enough instances for balancing, using original data");
+ return data;
+ }
+
+ try {
+ return applyAdvancedBalancing(data);
+ } catch (Exception e) {
+ ClassifierLogger.log("Advanced balancing failed: " + e.getMessage());
+ ClassifierLogger.log("Falling back to basic SMOTE");
+ return applyBasicSMOTE(data);
+ }
+ }
+
+ private Instances applyAdvancedBalancing(Instances data) throws Exception {
+ int maxClassSize = 0;
+ Map classInstanceCounts = new HashMap<>();
+
+ for (int i = 0; i < data.numInstances(); i++) {
+ double classValue = data.instance(i).classValue();
+ classInstanceCounts.merge(classValue, 1, Integer::sum);
+ maxClassSize = Math.max(maxClassSize, classInstanceCounts.get(classValue));
+ }
- ArrayList eventTypes = new ArrayList<>(uniqueEventTypes);
+ Instances balancedData = new Instances(data, 0);
- attributes.add(new Attribute("eventType", eventTypes));
- Instances trainingData = new Instances("EventData", attributes, 0);
- trainingData.setClassIndex(trainingData.numAttributes() - 1);
+ for (double classValue : classInstanceCounts.keySet()) {
+ Instances classData = extractClassInstances(data, classValue);
+ int targetSize = maxClassSize / 2;
- for(Event event: events){
- if(event.getEventType() != null){
- addInstance(trainingData, event);
- }
+ if (classInstanceCounts.get(classValue) > targetSize) {
+ balancedData.addAll(downsampleClass(classData, targetSize));
+ } else {
+ balancedData.addAll(oversampleClassWithSMOTE(classData, targetSize));
}
+ }
+
+ logBalancedDistribution(balancedData);
+ return balancedData;
+ }
+
+ private Instances extractClassInstances(Instances data, double classValue) {
+ Instances classData = new Instances(data, 0);
+ for (int i = 0; i < data.numInstances(); i++) {
+ if (data.instance(i).classValue() == classValue) {
+ classData.add(data.instance(i));
+ }
+ }
+ return classData;
+ }
+
+ private Instances downsampleClass(Instances classData, int targetSize) {
+ classData.randomize(new Random(RANDOM_SEED));
+ return new Instances(classData, 0, Math.min(targetSize, classData.numInstances()));
+ }
+
+ private Instances oversampleClassWithSMOTE(Instances classData, int targetSize) throws Exception {
+ int currentSize = classData.numInstances();
+ int percentage = (targetSize * 100) / currentSize - 100;
- Instances filteredData = applyStringToWordVectorFilter(trainingData);
+ SMOTE smote = new SMOTE();
+ smote.setInputFormat(classData);
+ smote.setNearestNeighbors(Math.min(SMOTE_NEIGHBORS, currentSize - 1));
+ smote.setPercentage(Math.max(100, percentage));
- classifier = new RandomForest();
- classifier.setMaxDepth(0);
- classifier.buildClassifier(filteredData);
+ return Filter.useFilter(classData, smote);
+ }
- trainingHeader = new Instances(filteredData, 0);
+ private Instances applyBasicSMOTE(Instances data) {
+ try {
+ SMOTE smote = new SMOTE();
+ smote.setInputFormat(data);
+ smote.setNearestNeighbors(SMOTE_NEIGHBORS);
+ smote.setPercentage(SMOTE_PERCENTAGE);
+
+ Instances balancedData = Filter.useFilter(data, smote);
+ logBalancedDistribution(balancedData);
+ return balancedData;
} catch (Exception e) {
- throw new RuntimeException("Failed to train model: " + e.getMessage(),e);
+ ClassifierLogger.log("SMOTE failed, using original data: " + e.getMessage());
+ return data;
}
}
- private Instances applyStringToWordVectorFilter(Instances data) throws Exception {
- StringToWordVector filter = new StringToWordVector();
- filter.setAttributeIndices("1,2,3,4,5,6");
- filter.setWordsToKeep(1000);
- filter.setLowerCaseTokens(true);
- filter.setInputFormat(data);
- return Filter.useFilter(data, filter);
+ private void logBalancedDistribution(Instances data) {
+ Map balancedCounts = new HashMap<>();
+ for (int i = 0; i < data.numInstances(); i++) {
+ double classValue = data.instance(i).classValue();
+ balancedCounts.merge(classValue, 1, Integer::sum);
+ }
+
+ ClassifierLogger.log("Class distribution after balancing:");
+ balancedCounts.forEach((classVal, count) -> {
+ String className = data.classAttribute().value(classVal.intValue());
+ ClassifierLogger.log(String.format(" %s: %d (%.1f%%)",
+ className, count, (double) count * 100 / data.numInstances()));
+ });
+ }
+
+ private void configureAdaBoost(AdaBoostM1 adaBoost) {
+ adaBoost.setDebug(false);
+ adaBoost.setNumIterations(15); // Can experiment with 10-20
+ adaBoost.setWeightThreshold(50);
+ adaBoost.setUseResampling(false);
+ adaBoost.setResume(false);
+ }
+
+// private void trainClassifier(Instances data) throws Exception {
+// // 1. Create base classifier
+// NaiveBayes nb = new NaiveBayes();
+// nb.setUseKernelEstimator(false);
+// nb.setUseSupervisedDiscretization(true);
+//
+// // 2. Create AdaBoost with default parameters
+// AdaBoostM1 baseAdaBoost = new AdaBoostM1();
+// baseAdaBoost.setClassifier(nb);
+// baseAdaBoost.setSeed(RANDOM_SEED);
+//
+// // 3. Setup parameter search
+// CVParameterSelection ps = new CVParameterSelection();
+// ps.setClassifier(baseAdaBoost);
+// ps.setNumFolds(5); // 5-fold cross-validation
+//
+// // Fix: Correct parameter flag for iterations (should be "I" not "P")
+// ps.addCVParameter("I 10 20 10"); // Tests 10, 15, 20 iterations
+//
+// // Optional: Search weightThreshold too
+// // ps.addCVParameter("W 50 150 50"); // Tests 50, 100, 150
+//
+// // 4. Run optimization
+// ps.buildClassifier(data);
+//
+// // 5. Apply best parameters manually
+// classifier = new AdaBoostM1();
+// classifier.setClassifier(nb);
+//
+// // Get best options and apply them
+// String[] bestOptions = ps.getBestClassifierOptions();
+// classifier.setOptions(bestOptions);
+//
+// // Fix: Train the classifier with the optimized parameters
+// classifier.buildClassifier(data);
+//
+// // 6. Log the selected parameters
+// ClassifierLogger.log("\n=== Optimized AdaBoost Parameters ===");
+// ClassifierLogger.log("Selected iterations: " + classifier.getNumIterations());
+// ClassifierLogger.log("Best configuration: " + Arrays.toString(bestOptions));
+//
+// trainingHeader = new Instances(data, 0);
+// }
+
+ private void trainClassifier(Instances data) throws Exception {
+ // 1. Create base classifier
+ NaiveBayes nb = new NaiveBayes();
+ nb.setUseKernelEstimator(false);
+ nb.setUseSupervisedDiscretization(true);
+
+ // 2. Perform manual parameter search for AdaBoost
+ int[] iterationsToTest = {5, 10, 15, 20, 25}; // The iterations we want to test
+ double bestAccuracy = 0.0;
+ int bestIterations = 10; // Default value
+
+ ClassifierLogger.log("\n=== Manual Parameter Search ===");
+
+ for (int iterations : iterationsToTest) {
+ // Create and configure a fresh AdaBoost instance for each test
+ AdaBoostM1 testBoost = new AdaBoostM1();
+ testBoost.setClassifier(nb);
+ testBoost.setSeed(RANDOM_SEED);
+ testBoost.setNumIterations(iterations);
+
+ // Evaluate using cross-validation
+ Evaluation eval = new Evaluation(data);
+ eval.crossValidateModel(testBoost, data, 5, new Random(RANDOM_SEED));
+
+ double accuracy = eval.pctCorrect();
+ ClassifierLogger.log(String.format(" Iterations=%d, Accuracy=%.2f%%",
+ iterations, accuracy));
+
+ // Keep track of the best configuration
+ if (accuracy > bestAccuracy) {
+ bestAccuracy = accuracy;
+ bestIterations = iterations;
+ }
+ }
+
+ // 3. Create the final classifier with the best parameters
+ classifier = new AdaBoostM1();
+ classifier.setClassifier(nb);
+ classifier.setSeed(RANDOM_SEED);
+ classifier.setNumIterations(bestIterations);
+
+ // Train the final model
+ classifier.buildClassifier(data);
+
+ // 4. Log the selected parameters
+ ClassifierLogger.log("\n=== Optimized AdaBoost Parameters ===");
+ ClassifierLogger.log("Selected iterations: " + bestIterations);
+ ClassifierLogger.log("Best accuracy: " + String.format("%.2f%%", bestAccuracy));
+ ClassifierLogger.log("Using classifier: " + classifier.getClassifier().getClass().getSimpleName());
+
+ trainingHeader = new Instances(data, 0);
+ }
+
+ private void evaluateModel(Instances data) throws Exception {
+ Evaluation eval = new Evaluation(data);
+ eval.crossValidateModel(classifier, data, 10, new Random(RANDOM_SEED));
+
+ ClassifierLogger.log("=== Classification Results ===");
+ ClassifierLogger.log(eval.toSummaryString());
+
+ ClassifierLogger.log("\n=== Detailed Accuracy By Class ===");
+ ClassifierLogger.log(eval.toClassDetailsString());
+
+ ClassifierLogger.log("\n=== Confusion Matrix ===");
+ ClassifierLogger.log(eval.toMatrixString());
}
private ArrayList createAttributes() {
ArrayList attributes = new ArrayList<>();
+
+ // Text attributes
attributes.add(new Attribute("title", (List) null));
attributes.add(new Attribute("description", (List) null));
attributes.add(new Attribute("hostName", (List) null));
attributes.add(new Attribute("location", (List) null));
- attributes.add(new Attribute("eventCapacity")); // Numeric
- attributes.add(new Attribute("eventMonth"));
- return attributes;
- }
-
- private Instances createFilteredInstance(Event event) throws Exception {
- ArrayList originalAttributes = createAttributes();
- originalAttributes.add(trainingHeader.classAttribute());
-
- Calendar cal = Calendar.getInstance();
- cal.setTime(event.getEventDate());
- int month = cal.get(Calendar.MONTH) + 1;
- Instances tempData = new Instances("TempData", originalAttributes, 0);
- tempData.setClassIndex(tempData.numAttributes() - 1);
-
- double[] values = addValuesToDataset(event, month, tempData);
- values[6] = 0;
+ // Numeric attributes
+ attributes.add(new Attribute("eventCapacity"));
+ attributes.add(new Attribute("eventMonth"));
+ attributes.add(new Attribute("eventDayOfWeek"));
+ attributes.add(new Attribute("isWeekend"));
- tempData.add(new DenseInstance(1.0, values));
- return applyStringToWordVectorFilter(tempData);
- }
+ // Enhanced features
+ attributes.add(new Attribute("titleLength"));
+ attributes.add(new Attribute("descriptionWordCount"));
+ attributes.add(new Attribute("hasWorkshopInTitle")); // Binary
+ attributes.add(new Attribute("hasWorkshopInDesc")); // Binary
+ attributes.add(new Attribute("hasHandsOnInDesc")); // Binary
- private double[] addValuesToDataset(Event event, int month, Instances tempData) {
- double[] values = new double[tempData.numAttributes()];
- values[0] = tempData.attribute(0).addStringValue(Objects.toString(event.getTitle(), ""));
- values[1] = tempData.attribute(1).addStringValue(Objects.toString(event.getDescription(), ""));
- values[2] = tempData.attribute(2).addStringValue(Objects.toString(event.getEventHost(), ""));
- values[3] = tempData.attribute(3).addStringValue(Objects.toString(event.getEventLocation(), ""));
- values[4] = event.getEventCapacity();
- values[5] = month;
- return values;
+ return attributes;
}
private void addInstance(Instances data, Event event) {
- if (data.numAttributes() < 7) {
- throw new IllegalStateException("Dataset does not have enough attributes! Found: " + data.numAttributes());
- }
+ double[] values = new double[data.numAttributes()];
+ // Basic features
+ values[0] = data.attribute(0).addStringValue(preprocessText(event.getTitle()));
+ values[1] = data.attribute(1).addStringValue(preprocessText(event.getDescription()));
+ values[2] = data.attribute(2).addStringValue(preprocessText(event.getEventHost()));
+ values[3] = data.attribute(3).addStringValue(preprocessText(event.getEventLocation()));
+ values[4] = event.getEventCapacity();
+
+ // Temporal features
Calendar cal = Calendar.getInstance();
cal.setTime(event.getEventDate());
- int month = cal.get(Calendar.MONTH) + 1;
+ values[5] = cal.get(Calendar.MONTH) + 1;
+ values[6] = cal.get(Calendar.DAY_OF_WEEK);
+ values[7] = (values[6] == Calendar.SUNDAY || values[6] == Calendar.SATURDAY) ? 1 : 0;
- int eventTypeIndex = data.attribute(6).indexOfValue(event.getEventType());
- if (eventTypeIndex == -1) {
- System.out.println("Warning: Event type not found - " + event.getEventType());
- eventTypeIndex = 0;
- }
+ // Text features
+ String title = event.getTitle() != null ? event.getTitle().toLowerCase() : "";
+ String desc = event.getDescription() != null ? event.getDescription().toLowerCase() : "";
+ values[8] = title.length();
+ values[9] = desc.split("\\s+").length;
+
+ // Workshop-specific features
+ values[10] = title.contains("workshop") ? 1 : 0;
+ values[11] = desc.contains("workshop") ? 1 : 0;
+ values[12] = desc.contains("hands-on") || desc.contains("practical") ? 1 : 0;
+
+ // Class value
+ values[data.classIndex()] = data.classAttribute().indexOfValue(event.getEventType());
- double[] values = addValuesToDataset(event, month, data);
- values[6] = eventTypeIndex;
data.add(new DenseInstance(1.0, values));
}
+ private String preprocessText(String text) {
+ if (text == null) return "";
+ return text.toLowerCase().replaceAll("\\s+", " ").trim();
+ }
public String predictEventType(Event event) {
try {
-
- Instance instance = new DenseInstance(trainingHeader.numAttributes());
- instance.setDataset(trainingHeader);
-
Instances filteredData = createFilteredInstance(event);
-
double prediction = classifier.classifyInstance(filteredData.firstInstance());
return trainingHeader.classAttribute().value((int) prediction);
-
} catch (Exception e) {
throw new RuntimeException("Failed to make prediction: " + e.getMessage());
}
@@ -138,24 +444,69 @@ public String predictEventType(Event event) {
public Map getPredictionProbabilities(Event event) {
try {
-
- ArrayList originalAttributes = createAttributes();
- originalAttributes.add(trainingHeader.classAttribute());
-
Instances filteredData = createFilteredInstance(event);
-
double[] distributions = classifier.distributionForInstance(filteredData.firstInstance());
- Map probabilities = new HashMap<>();
+ Map probabilities = new HashMap<>();
for (int i = 0; i < distributions.length; i++) {
String type = trainingHeader.classAttribute().value(i);
probabilities.put(type, distributions[i]);
}
-
return probabilities;
-
} catch (Exception e) {
throw new RuntimeException("Failed to get prediction probabilities: " + e.getMessage());
}
}
-}
+
+ private Instances createFilteredInstance(Event event) throws Exception {
+ ArrayList originalAttributes = createAttributes();
+ originalAttributes.add(trainingHeader.classAttribute());
+
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(event.getEventDate());
+ int month = cal.get(Calendar.MONTH) + 1;
+
+ Instances tempData = new Instances("TempData", originalAttributes, 0);
+ tempData.setClassIndex(tempData.numAttributes() - 1);
+
+ double[] values = addValuesToDataset(event, month, tempData);
+ values[6] = 0; // Reset day of week for prediction
+
+ tempData.add(new DenseInstance(1.0, values));
+ return applyTextProcessingFilters(tempData);
+ }
+
+ private double[] addValuesToDataset(Event event, int month, Instances tempData) {
+ double[] values = new double[tempData.numAttributes()];
+
+ // Basic features
+ values[0] = tempData.attribute(0).addStringValue(Objects.toString(event.getTitle(), ""));
+ values[1] = tempData.attribute(1).addStringValue(Objects.toString(event.getDescription(), ""));
+ values[2] = tempData.attribute(2).addStringValue(Objects.toString(event.getEventHost(), ""));
+ values[3] = tempData.attribute(3).addStringValue(Objects.toString(event.getEventLocation(), ""));
+ values[4] = event.getEventCapacity();
+
+ // Temporal features
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(event.getEventDate());
+ values[5] = month;
+ values[6] = cal.get(Calendar.DAY_OF_WEEK);
+ values[7] = (values[6] == 1 || values[6] == 7) ? 1 : 0; // isWeekend
+
+ // Textual features
+ String title = Objects.toString(event.getTitle(), "");
+ String description = Objects.toString(event.getDescription(), "");
+ values[8] = title.length();
+ values[9] = description.split("\\s+").length;
+
+ return values;
+ }
+
+ public void saveModel(String filePath) throws Exception {
+ SerializationHelper.write(filePath, classifier);
+ }
+
+ public Instances getTrainingData() {
+ return new Instances(trainingHeader);
+ }
+}
\ No newline at end of file
diff --git a/emmas/src/main/java/dev/brianweloba/weka/RunTest.java b/emmas/src/main/java/dev/brianweloba/weka/RunTest.java
new file mode 100644
index 0000000..e1ff1e3
--- /dev/null
+++ b/emmas/src/main/java/dev/brianweloba/weka/RunTest.java
@@ -0,0 +1,26 @@
+package dev.brianweloba.weka;
+
+import dev.brianweloba.dao.EventDAO;
+import dev.brianweloba.model.Event;
+
+import java.util.List;
+
+public class RunTest {
+ public static void main(String[] args) {
+ try {
+ List allEvents = loadAllEvents();
+ EventClassifierTester tester = new EventClassifierTester("improved_classifier_results");
+
+ tester.runFullTest(allEvents);
+ ClassifierLogger.log("Testing completed successfully");
+ } catch (Exception e) {
+ ClassifierLogger.log("Fatal error in testing: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ private static List loadAllEvents() {
+ EventDAO eventDAO = new EventDAO();
+ return eventDAO.findAll();
+ }
+}
diff --git a/emmas/src/main/resources/META-INF/persistence.xml b/emmas/src/main/resources/META-INF/persistence.xml
index 1de4697..fa78cff 100644
--- a/emmas/src/main/resources/META-INF/persistence.xml
+++ b/emmas/src/main/resources/META-INF/persistence.xml
@@ -8,6 +8,7 @@
dev.brianweloba.model.Event
dev.brianweloba.model.RSVP
+ dev.brianweloba.model.User
false
diff --git a/emmas/src/main/webapp/WEB-INF/components/footer.jsp b/emmas/src/main/webapp/WEB-INF/components/footer.jsp
index 376a0b6..a3583d0 100644
--- a/emmas/src/main/webapp/WEB-INF/components/footer.jsp
+++ b/emmas/src/main/webapp/WEB-INF/components/footer.jsp
@@ -79,7 +79,7 @@
Designed by Brian Weloba
- © <%= new java.util.Date().getYear() + 1900 %> Emma's. All rights reserved.
+ © <%= java.time.LocalDate.now().getYear() %> Emma's. All rights reserved.
diff --git a/emmas/src/main/webapp/WEB-INF/components/navbar.jsp b/emmas/src/main/webapp/WEB-INF/components/navbar.jsp
index c749545..b3c5ef6 100644
--- a/emmas/src/main/webapp/WEB-INF/components/navbar.jsp
+++ b/emmas/src/main/webapp/WEB-INF/components/navbar.jsp
@@ -2,40 +2,48 @@