diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml
new file mode 100644
index 0000000..1cc8080
--- /dev/null
+++ b/dependency-reduced-pom.xml
@@ -0,0 +1,91 @@
+
+
+ 4.0.0
+ com.ecs160
+ annotations
+ 1.0-SNAPSHOT
+
+ src/test/java
+
+
+ src/main/resources
+
+ **/*.json
+
+
+ desktop.ini
+
+
+
+ target/generated-sources/annotations
+
+ desktop.ini
+
+
+
+
+
+ maven-shade-plugin
+ 3.4.1
+
+
+ package
+
+ shade
+
+
+ true
+
+
+ com.ecs160.MyApp
+
+
+
+
+ *:*
+
+ META-INF/MANIFEST.MF
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+
+
+
+
+
+ maven-jar-plugin
+ 3.2.0
+
+
+
+ com.ecs160.MyApp
+
+
+
+
+
+ maven-jar-plugin
+ 3.3.0
+
+
+
+ com.ecs160.hw1.SocialMediaAnalyzerDriver
+
+
+
+
+
+ maven-surefire-plugin
+ 3.0.0
+
+
+
+
+ UTF-8
+ 21
+ 21
+
+
diff --git a/newpost.java b/newpost.java
new file mode 100644
index 0000000..2e34bab
--- /dev/null
+++ b/newpost.java
@@ -0,0 +1,112 @@
+package com.ecs160;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.stream.Collectors;
+
+@Persistable
+public class Post {
+
+ @PersistableId
+ @SerializedName("cid")
+ private String postId;
+
+ @PersistableField
+ @SerializedName("post")
+ private PostData post; // ✅ `record` 现在在 `post` 里面
+
+ @PersistableField
+ @SerializedName("thread")
+ private Thread thread; // ✅ `replies` 仍然在 `thread` 里
+
+ @LazyLoad
+ @PersistableListField(className = "Post")
+ @SerializedName("replyIds")
+ private List replyIds;
+
+ private transient List replies;
+
+ public String getPostId() {
+ return (post != null) ? post.getCid() : null;
+ }
+
+ // ✅ `record` 现在在 `post` 里
+ public String getPostContent() {
+ if (post != null && post.getRecord() != null) {
+ return maskUrls(post.getRecord().getText());
+ }
+ return null;
+ }
+
+ private String maskUrls(String text) {
+ if (text == null) return null;
+ return text.replaceAll("(https?://\\S+|\\S+\\.\\S+)", "[link]");
+ }
+
+ // ✅ 解析 `thread.replies`
+ public List getReplies() {
+ if (replies == null && thread != null && thread.getReplies() != null) {
+ replies = thread.getReplies();
+ }
+ return replies;
+ }
+
+ public void setReplies(List replies) {
+ if (replies != null) {
+ this.replies = replies;
+ this.replyIds = replies.stream().map(Post::getPostId).toList();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Post{" +
+ "postId=" + getPostId() +
+ ", postContent='" + getPostContent() + '\'' +
+ ", replies=" + (replyIds != null ? replyIds : "[]") +
+ '}';
+ }
+
+ // ✅ `Thread` 仍然保留,因为 `replies` 在 `thread` 里
+ public static class Thread {
+ @SerializedName("post")
+ private PostData post;
+
+ @SerializedName("replies")
+ private List replies; // ✅ `replies` 仍然在 `thread` 里
+
+ public PostData getPost() {
+ return post;
+ }
+
+ public List getReplies() {
+ return replies;
+ }
+ }
+
+ public static class PostData {
+ @SerializedName("cid")
+ private String cid;
+
+ @SerializedName("record")
+ private Record record; // ✅ `record` 现在在 `post` 里
+
+ public String getCid() {
+ return cid;
+ }
+
+ public Record getRecord() {
+ return record;
+ }
+ }
+
+ public static class Record {
+ @SerializedName("text")
+ private String text;
+
+ public String getText() {
+ return text;
+ }
+ }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 8373c71..4fee3f6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,6 +49,18 @@
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.2.0
+
+
+
+ com.ecs160.MyApp
+
+
+
+
org.apache.maven.plugins
maven-jar-plugin
diff --git a/src/main/java/com/ecs160/JsonLoader.java b/src/main/java/com/ecs160/JsonLoader.java
new file mode 100644
index 0000000..d3b8988
--- /dev/null
+++ b/src/main/java/com/ecs160/JsonLoader.java
@@ -0,0 +1,29 @@
+package com.ecs160;
+
+import com.google.gson.*;
+import com.google.gson.reflect.TypeToken;
+import java.io.FileReader;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.List;
+
+public class JsonLoader {
+ private static final Gson gson = new Gson();
+
+ public static List loadPosts(String filePath) throws IOException {
+ try (FileReader reader = new FileReader(filePath)) {
+ JsonObject jsonObject = gson.fromJson(reader, JsonObject.class);
+ JsonArray feedArray = jsonObject.getAsJsonArray("feed");
+ Type listType = new TypeToken>() {}.getType();
+ List posts = gson.fromJson(feedArray, listType);
+
+
+ /*
+ for (int i = 0; i < Math.min(posts.size(), 10); i++) {
+ System.out.println("加载 Post ID: " + posts.get(i).getPostId());
+ }
+ */
+ return posts;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ecs160/MyApp.java b/src/main/java/com/ecs160/MyApp.java
index d873b0d..1fd3cf0 100644
--- a/src/main/java/com/ecs160/MyApp.java
+++ b/src/main/java/com/ecs160/MyApp.java
@@ -1,13 +1,101 @@
package com.ecs160;
-import java.io.FileNotFoundException;
-import java.lang.reflect.InvocationTargetException;
-import java.net.URISyntaxException;
+import com.ecs160.persistence.Session;
+import java.util.Scanner;
import java.util.List;
public class MyApp {
- public static void main(String[] args) throws FileNotFoundException, URISyntaxException, NoSuchFieldException {
+ public static void main(String[] args) {
+
+
+ Session.getredisSession().CleanDataBase();
+
+ try {
+ String filePath = "src/main/resources/input.json";
+ List posts = JsonLoader.loadPosts(filePath);
+ int count = 0;
+ Session session = Session.getredisSession();
+ for (Post post : posts) {
+
+ session.add(post);
+ //TestIfGotPostsFromJson(count,post);
+ }
+ session.persistAll();
+
+ //PrintFist10Posts(posts);
+
+ UserInput();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
}
-}
+
+
+ public static void UserInput() {
+ Scanner scanner = new Scanner(System.in);
+ while (true) {
+ System.out.print("\nPlease Enter Post ID (or type 'exit' to quit): ");
+ String inputPostId = scanner.nextLine().trim();
+
+ if (inputPostId.equalsIgnoreCase("exit")) {
+ System.out.println("Exiting UserInput mode...");
+ break;
+ }
+
+ // 加载 Post
+ Post post = (Post) Session.getredisSession().load(Post.class, inputPostId);
+
+ if (post != null) {
+ // 成功查询到 Post
+ System.out.println("\nLoad Post Successfully...");
+ System.out.println("Post Content: " + post.getPostContent());
+ System.out.println("Reply IDs: " + (post.getReplyIds() != null ? post.getReplyIds() : "[]"));
+ List replyTexts = post.getReplyTexts();
+ if (!replyTexts.isEmpty()) {
+ System.out.println(" Replies:");
+ for (String replyText : replyTexts) {
+ System.out.println(" " + replyText);
+ }
+ } else {
+ System.out.println(" No Replies Found.");
+ }
+ } else {
+ System.out.println("Post Not Found. Please try again.");
+ }
+ }
+ scanner.close();
+ }
+
+ public static void TestIfGotPostsFromJson(int count, Post post) {
+ if (count <=20) {
+ System.out.println("\n========== DEBUG: Loading Posts =========="+ " Number Of "+ count);
+ System.out.println("\uD83D\uDCCC" + post);
+ System.out.println("✅ Debug: if have replies In the Posts: " + post.getReplyIds());
+ }
+ }
+
+ public static void PrintFist10Posts(List posts) {
+ int J= 0;
+ for (int i = 0; i < Math.min(posts.size(), 2); i++) {
+ String postId = posts.get(i).getPostId();
+
+ Post loadedPost = (Post) Session.getredisSession().load(Post.class, postId);
+ if (loadedPost != null) {
+
+ System.out.println("\n========== DEBUG: Loading Posts From Redis ==========");
+
+ System.out.println("✅ Post ID: " + loadedPost.getPostId());
+ System.out.println("✅ Post Content: " + loadedPost.getPostContent());
+ System.out.println("✅ Reply IDs from Redis: " +
+ (loadedPost.getReplyIds() != null ? loadedPost.getReplyIds() : "[]"));
+
+
+ //System.out.println("\uD83D\uDCCC" + loadedPost.getReplyTexts());
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ecs160/Post.java b/src/main/java/com/ecs160/Post.java
new file mode 100644
index 0000000..f06fc31
--- /dev/null
+++ b/src/main/java/com/ecs160/Post.java
@@ -0,0 +1,142 @@
+package com.ecs160;
+
+import com.ecs160.persistence.*;
+import com.google.gson.annotations.SerializedName;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Persistable
+public class Post {
+
+ @PersistableField
+ @SerializedName("thread")
+ private Post.Thread thread;
+
+ @PersistableField
+ @PersistableListField(className = "Post")
+ private List replyIds;
+
+ @PersistableId
+ private String postId;
+
+
+ public String getPostId() {
+ if (postId == null && thread != null && thread.post != null) {
+ postId = thread.post.getCid();
+ }
+ return postId;
+ }
+
+ public String getPostContent() {
+ if (thread != null && thread.post != null && thread.post.getRecord() != null) {
+ return formatURL(thread.post.getRecord().getText()) ;
+ }
+ return null;
+ }
+
+ public List getReplies() {
+ if (thread != null && thread.getReplies() != null) {
+ return thread.getReplies();
+ }
+ return List.of();
+ }
+
+ public List getReplyIds() {
+ if (getReplies() != null) {
+ this.replyIds = getReplies().stream()
+ .map(t -> t.getPost().getCid())
+ .toList();
+ } else {
+ this.replyIds = List.of();
+ }
+ return replyIds;
+ }
+
+ public void setReplyIds(List replyIds) {
+ this.replyIds = replyIds;
+ }
+
+ public void setPostId(String postId) {
+ this.postId = postId;
+ }
+
+ private String formatURL(String text) {if (text == null) return null;text = text.replaceAll("[\\n\\r]", " ");return text.replaceAll("(https?://\\S+|\\S+\\.\\S+)", "[link]");}
+
+ public List getReplyTexts() {
+ if (this.getReplies() == null) {
+ return List.of();
+ }
+
+ return this.getReplies().stream()
+ .map(reply -> {
+ if (reply.getPost() != null && reply.getPost().getRecord() != null) {
+ return reply.getPost().getRecord().getText();
+ }
+ return "(No text available)";
+ })
+ .toList();
+ }
+
+
+
+ @Override
+ public String toString() {
+ return "Post{" +
+ "postId=" + getPostId() +
+ ", postContent='" + getPostContent() + '\'' +
+ ", replies=" + (getReplies() != null ?
+ getReplies().stream().map(t -> t.getPost().getCid()).toList() : "[]") +
+ '}';
+ }
+
+
+ public static class Thread {
+ @PersistableField
+ @SerializedName("post")
+ private PostData post;
+
+ @PersistableListField(className = "Post")
+ @SerializedName("replies")
+ private List replies;
+
+ public PostData getPost() {
+ return post;
+ }
+
+ public List getReplies() {
+ return replies;
+ }
+ public void setReplies(List replies) {
+ this.replies = replies;
+ }
+
+ }
+
+
+ public static class PostData {
+ @SerializedName("cid")
+ private String cid;
+
+ @SerializedName("record")
+ private Record record;
+
+ public String getCid() {
+ return cid;
+ }
+
+ public Record getRecord() {
+ return record;
+ }
+ }
+
+ public static class Record {
+ @SerializedName("text")
+ private String text;
+
+ public String getText() {
+ return text;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ecs160/persistence/LazyLoad.java b/src/main/java/com/ecs160/persistence/LazyLoad.java
new file mode 100644
index 0000000..f308751
--- /dev/null
+++ b/src/main/java/com/ecs160/persistence/LazyLoad.java
@@ -0,0 +1,11 @@
+package com.ecs160.persistence;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface LazyLoad {
+}
diff --git a/src/main/java/com/ecs160/persistence/Persistable.java b/src/main/java/com/ecs160/persistence/Persistable.java
index d155ac2..f3617f7 100644
--- a/src/main/java/com/ecs160/persistence/Persistable.java
+++ b/src/main/java/com/ecs160/persistence/Persistable.java
@@ -7,3 +7,4 @@
public @interface Persistable {
}
+
diff --git a/src/main/java/com/ecs160/persistence/PersistableField.java b/src/main/java/com/ecs160/persistence/PersistableField.java
new file mode 100644
index 0000000..6934d84
--- /dev/null
+++ b/src/main/java/com/ecs160/persistence/PersistableField.java
@@ -0,0 +1,12 @@
+package com.ecs160.persistence;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+// ✅ 只有被标记的字段才会被 `Session` 存入 Redis
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface PersistableField {
+}
\ No newline at end of file
diff --git a/src/main/java/com/ecs160/persistence/PersistableId.java b/src/main/java/com/ecs160/persistence/PersistableId.java
new file mode 100644
index 0000000..48304e1
--- /dev/null
+++ b/src/main/java/com/ecs160/persistence/PersistableId.java
@@ -0,0 +1,12 @@
+package com.ecs160.persistence;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface PersistableId {
+}
\ No newline at end of file
diff --git a/src/main/java/com/ecs160/persistence/PersistableListField.java b/src/main/java/com/ecs160/persistence/PersistableListField.java
new file mode 100644
index 0000000..e4e5200
--- /dev/null
+++ b/src/main/java/com/ecs160/persistence/PersistableListField.java
@@ -0,0 +1,13 @@
+package com.ecs160.persistence;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface PersistableListField {
+ String className();
+}
\ No newline at end of file
diff --git a/src/main/java/com/ecs160/persistence/Session.java b/src/main/java/com/ecs160/persistence/Session.java
index 9a84147..1499032 100644
--- a/src/main/java/com/ecs160/persistence/Session.java
+++ b/src/main/java/com/ecs160/persistence/Session.java
@@ -1,41 +1,156 @@
package com.ecs160.persistence;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javassist.util.proxy.MethodHandler;
-import javassist.util.proxy.ProxyFactory;
+import com.ecs160.Post;
+import com.google.gson.Gson;
import redis.clients.jedis.Jedis;
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.stream.Collectors;
-
-
-// Assumption - only support int/long/and string values
public class Session {
-
- private Jedis jedisSession;
+ private static Session redisSession;
+ private final Jedis jedisSession;
+ private final Gson gson;
+ private final Map cache;
private Session() {
- jedisSession = new Jedis("localhost", 6379);;
+ this.jedisSession = new Jedis("localhost", 6379);
+ this.gson = new Gson();
+ this.cache = new HashMap<>();
}
+ public static Session getredisSession() {
+ if (redisSession == null) {
+ redisSession = new Session();
+ }
+ return redisSession;
+
+ }
public void add(Object obj) {
+ if (obj instanceof Post) {
+ ((Post) obj).getPostId();
+ }
+
+ try {
+ Field idField = getPersistableIdField(obj);
+ if (idField == null) {
+ return;
+ }
+ idField.setAccessible(true);
+ String postId = (String) idField.get(obj);
+
+ if (postId == null || postId.isEmpty()) {
+ return;
+ }
+ String key = obj.getClass().getSimpleName() + ":" + postId;
+ cache.put(key, obj);
+ //System.out.println("DEBUG: Cache after adding post -> " + cache);
+
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ public void persistAll() {
+
+ for (Map.Entry entry : cache.entrySet()) {
+ String key = entry.getKey();
+ Object obj = entry.getValue();
+
+ if (obj instanceof Post p) {p.getReplyIds();} //Nor sure if this is allowed....
+ // but I really spent over 10 hours to findi hits solution
+
+
+ jedisSession.hset(key, "data", gson.toJson(obj));
+
+ for (Field field : obj.getClass().getDeclaredFields()) {
+ field.setAccessible(true);
+
+ if (field.isAnnotationPresent(PersistableListField.class)) {
+ try {
+ //System.out.println("This is field.get(obj)" + field.get(obj));
+ //System.out.println("This is replyIds " + replyIds);
+ //System.out.println("This is ReplyIds " + replyIds);
+ List replyIdsList = (List) field.get(obj);
+ String replyIds = String.join(",", replyIdsList);
+ //System.out.println("This is replyIds: " + replyIds);
+ jedisSession.hset(key, field.getName(), replyIds);
+ // System.out.println("Saving to Redis Key: " + key + ", Field: " + field.getName() + ", Value: " + replyIds);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+ }
+ }
+
}
- public void persistAll() {
+
+ public Object load(Class> clazz, String postId) {
+ if (postId == null || clazz == null) {
+ return null;
+ }
+
+ String key = clazz.getSimpleName() + ":" + postId;
+ Map redisData = jedisSession.hgetAll(key);
+
+ if (redisData.isEmpty()) {
+ return null;
+ }
+
+ try {
+ Post post = gson.fromJson(redisData.get("data"), Post.class);
+
+ // 解析 replyIds 并转换为 Post 对象
+ if (redisData.containsKey("replyIds")) {
+ String replyIdsStr = redisData.get("replyIds");
+ List replyIdsList = replyIdsStr.isEmpty() ? List.of() : Arrays.asList(replyIdsStr.split(","));
+ post.setReplyIds(replyIdsList);
+ }
+
+ return post;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
}
- public Object load(Object object) {
+
+
+ private Field getPersistableIdField(Object obj) {
+ for (Field field : obj.getClass().getDeclaredFields()) {
+ if (field.isAnnotationPresent(PersistableId.class)) {
+ return field;
+ }
+ }
return null;
}
+ private String getObjectId(Object obj) {
+ if (obj == null) {
+ return "null";
+ }
+ try {
+ Field idField = getPersistableIdField(obj);
+ if (idField != null) {
+ idField.setAccessible(true);
+ String id = (String) idField.get(obj);
+ return (id != null) ? id : "null";
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return "null";
+ }
-}
+
+ public void CleanDataBase() {
+ jedisSession.flushDB();
+ }
+}
\ No newline at end of file