messages) {
try {
- JsonSerializer serializer = SerializerFactory.getInstance();
- return serializer.serializeArray(messages);
+ return mJsonSerializer.serializeArray(messages);
} catch (IOException e) {
// Fallback to original implementation if serialization fails
logger.log(Level.WARNING, "JSON serialization failed unexpectedly; falling back to org.json implementation", e);
- JSONArray array = new JSONArray();
- for (JSONObject message:messages) {
- array.put(message);
- }
- return array.toString();
+ return mDefaultJsonSerializer.serializeArray(messages);
}
}
@@ -607,4 +633,109 @@ public void close() {
}
}
+ /**
+ * Builder class for constructing a MixpanelAPI instance with optional configuration.
+ *
+ * The Builder pattern provides a flexible way to configure MixpanelAPI with various
+ * options including custom endpoints, gzip compression, feature flags, and JSON serializers.
+ *
+ * @since 1.6.1
+ */
+ public static class Builder {
+ private String eventsEndpoint;
+ private String peopleEndpoint;
+ private String groupsEndpoint;
+ private String importEndpoint;
+ private boolean useGzipCompression;
+ private BaseFlagsConfig flagsConfig;
+ private JsonSerializer jsonSerializer;
+
+ /**
+ * Sets the endpoint URL for Mixpanel events messages.
+ *
+ * @param eventsEndpoint the URL that will accept Mixpanel events messages
+ * @return this Builder instance for method chaining
+ */
+ public Builder eventsEndpoint(String eventsEndpoint) {
+ this.eventsEndpoint = eventsEndpoint;
+ return this;
+ }
+
+ /**
+ * Sets the endpoint URL for Mixpanel people messages.
+ *
+ * @param peopleEndpoint the URL that will accept Mixpanel people messages
+ * @return this Builder instance for method chaining
+ */
+ public Builder peopleEndpoint(String peopleEndpoint) {
+ this.peopleEndpoint = peopleEndpoint;
+ return this;
+ }
+
+ /**
+ * Sets the endpoint URL for Mixpanel groups messages.
+ *
+ * @param groupsEndpoint the URL that will accept Mixpanel groups messages
+ * @return this Builder instance for method chaining
+ */
+ public Builder groupsEndpoint(String groupsEndpoint) {
+ this.groupsEndpoint = groupsEndpoint;
+ return this;
+ }
+
+ /**
+ * Sets the endpoint URL for Mixpanel import messages.
+ *
+ * @param importEndpoint the URL that will accept Mixpanel import messages
+ * @return this Builder instance for method chaining
+ */
+ public Builder importEndpoint(String importEndpoint) {
+ this.importEndpoint = importEndpoint;
+ return this;
+ }
+
+ /**
+ * Sets whether to use gzip compression for network requests.
+ *
+ * @param useGzipCompression true to enable gzip compression, false otherwise
+ * @return this Builder instance for method chaining
+ */
+ public Builder useGzipCompression(boolean useGzipCompression) {
+ this.useGzipCompression = useGzipCompression;
+ return this;
+ }
+
+ /**
+ * Sets the configuration for feature flags evaluation.
+ * Accepts either LocalFlagsConfig or RemoteFlagsConfig.
+ *
+ * @param flagsConfig configuration for feature flags evaluation
+ * @return this Builder instance for method chaining
+ */
+ public Builder flagsConfig(BaseFlagsConfig flagsConfig) {
+ this.flagsConfig = flagsConfig;
+ return this;
+ }
+
+ /**
+ * Sets a custom JSON serializer for message serialization.
+ *
+ * @param jsonSerializer custom JSON serializer implementation
+ * @return this Builder instance for method chaining
+ */
+ public Builder jsonSerializer(JsonSerializer jsonSerializer) {
+ this.jsonSerializer = jsonSerializer;
+ return this;
+ }
+
+ /**
+ * Builds and returns a new MixpanelAPI instance with the configured settings.
+ *
+ * @return a new MixpanelAPI instance
+ */
+ public MixpanelAPI build() {
+ return new MixpanelAPI(this);
+ }
+ }
+
}
diff --git a/src/main/java/com/mixpanel/mixpanelapi/internal/JsonSerializer.java b/src/main/java/com/mixpanel/mixpanelapi/internal/JsonSerializer.java
index f7acf2e..d047b43 100644
--- a/src/main/java/com/mixpanel/mixpanelapi/internal/JsonSerializer.java
+++ b/src/main/java/com/mixpanel/mixpanelapi/internal/JsonSerializer.java
@@ -10,7 +10,7 @@
* This allows for different implementations (org.json, Jackson) to be used
* based on performance requirements and available dependencies.
*
- * @since 1.6.0
+ * @since 1.6.1
*/
public interface JsonSerializer {
@@ -22,23 +22,4 @@ public interface JsonSerializer {
* @throws IOException if serialization fails
*/
String serializeArray(List messages) throws IOException;
-
- /**
- * Serializes a list of JSONObjects directly to UTF-8 encoded bytes.
- * This method can be more efficient for large payloads as it avoids
- * the intermediate String creation.
- *
- * @param messages The list of JSONObjects to serialize
- * @return UTF-8 encoded bytes of the JSON array
- * @throws IOException if serialization fails
- */
- byte[] serializeArrayToBytes(List messages) throws IOException;
-
- /**
- * Returns the name of this serializer implementation.
- * Useful for logging and debugging purposes.
- *
- * @return The implementation name (e.g., "org.json", "Jackson")
- */
- String getImplementationName();
}
\ No newline at end of file
diff --git a/src/main/java/com/mixpanel/mixpanelapi/internal/OrgJsonSerializer.java b/src/main/java/com/mixpanel/mixpanelapi/internal/OrgJsonSerializer.java
index fa93526..9d0e268 100644
--- a/src/main/java/com/mixpanel/mixpanelapi/internal/OrgJsonSerializer.java
+++ b/src/main/java/com/mixpanel/mixpanelapi/internal/OrgJsonSerializer.java
@@ -2,20 +2,18 @@
import org.json.JSONArray;
import org.json.JSONObject;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* JSON serialization implementation using org.json library.
* This is the default implementation that maintains backward compatibility.
*
- * @since 1.6.0
+ * @since 1.6.1
*/
public class OrgJsonSerializer implements JsonSerializer {
@Override
- public String serializeArray(List messages) throws IOException {
+ public String serializeArray(List messages) {
if (messages == null || messages.isEmpty()) {
return "[]";
}
@@ -26,14 +24,4 @@ public String serializeArray(List messages) throws IOException {
}
return array.toString();
}
-
- @Override
- public byte[] serializeArrayToBytes(List messages) throws IOException {
- return serializeArray(messages).getBytes(StandardCharsets.UTF_8);
- }
-
- @Override
- public String getImplementationName() {
- return "org.json";
- }
}
\ No newline at end of file
diff --git a/src/main/java/com/mixpanel/mixpanelapi/internal/SerializerFactory.java b/src/main/java/com/mixpanel/mixpanelapi/internal/SerializerFactory.java
deleted file mode 100644
index 9a6d8b7..0000000
--- a/src/main/java/com/mixpanel/mixpanelapi/internal/SerializerFactory.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package com.mixpanel.mixpanelapi.internal;
-
-import java.util.logging.Logger;
-
-/**
- * Factory for creating JsonSerializer instances.
- * Automatically detects if Jackson is available on the classpath and returns
- * the appropriate implementation for optimal performance.
- *
- * @since 1.6.0
- */
-public class SerializerFactory {
-
- private static final Logger LOGGER = Logger.getLogger(SerializerFactory.class.getName());
- private static final boolean JACKSON_AVAILABLE;
- private static JsonSerializer instance;
-
- static {
- boolean jacksonFound = false;
- try {
- // Check if Jackson classes are available
- Class.forName("com.fasterxml.jackson.core.JsonFactory");
- Class.forName("com.fasterxml.jackson.core.JsonGenerator");
- jacksonFound = true;
- LOGGER.info("Jackson detected on classpath. High-performance JSON serialization will be used for large batches.");
- } catch (ClassNotFoundException e) {
- LOGGER.info("Jackson not found on classpath. Using standard org.json serialization. " +
- "Add jackson-databind dependency for improved performance with large batches.");
- }
- JACKSON_AVAILABLE = jacksonFound;
- }
-
- /**
- * Returns a singleton JsonSerializer instance.
- * If Jackson is available on the classpath, returns a JacksonSerializer for better performance.
- * Otherwise, returns an OrgJsonSerializer for compatibility.
- *
- * @return A JsonSerializer instance
- */
- public static synchronized JsonSerializer getInstance() {
- if (instance == null) {
- if (JACKSON_AVAILABLE) {
- try {
- instance = new JacksonSerializer();
- LOGGER.fine("Using Jackson serializer for high performance");
- } catch (NoClassDefFoundError e) {
- // Fallback if runtime loading fails
- LOGGER.warning("Failed to initialize Jackson serializer, falling back to org.json: " + e.getMessage());
- instance = new OrgJsonSerializer();
- }
- } else {
- instance = new OrgJsonSerializer();
- LOGGER.fine("Using org.json serializer");
- }
- }
- return instance;
- }
-
- /**
- * Checks if Jackson is available on the classpath.
- *
- * @return true if Jackson is available, false otherwise
- */
- public static boolean isJacksonAvailable() {
- return JACKSON_AVAILABLE;
- }
-
- /**
- * Gets the name of the current serializer implementation.
- *
- * @return The implementation name
- */
- public static String getCurrentImplementation() {
- return getInstance().getImplementationName();
- }
-
- /**
- * For testing purposes - allows resetting the singleton instance.
- * Should not be used in production code.
- */
- static void resetInstance() {
- instance = null;
- }
-}
\ No newline at end of file
diff --git a/src/test/java/com/mixpanel/mixpanelapi/MixpanelAPITest.java b/src/test/java/com/mixpanel/mixpanelapi/MixpanelAPITest.java
index d86155d..841054a 100644
--- a/src/test/java/com/mixpanel/mixpanelapi/MixpanelAPITest.java
+++ b/src/test/java/com/mixpanel/mixpanelapi/MixpanelAPITest.java
@@ -11,6 +11,10 @@
import java.util.Set;
import java.util.zip.GZIPInputStream;
+import com.mixpanel.mixpanelapi.featureflags.config.LocalFlagsConfig;
+import com.mixpanel.mixpanelapi.featureflags.config.RemoteFlagsConfig;
+import com.mixpanel.mixpanelapi.internal.OrgJsonSerializer;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -1119,4 +1123,281 @@ public boolean sendImportData(String dataString, String endpointUrl, String toke
}
}
+ /**
+ * Test builder with no options set uses default values
+ */
+ public void testBuilderWithDefaults() {
+ // WHEN
+ MixpanelAPI api = new MixpanelAPI.Builder().build();
+
+ // THEN
+ assertEquals(Config.BASE_ENDPOINT + "/track", api.mEventsEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/engage", api.mPeopleEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/groups", api.mGroupsEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/import", api.mImportEndpoint);
+ assertFalse(api.mUseGzipCompression);
+ assertNull(api.mLocalFlags);
+ assertNull(api.mRemoteFlags);
+ api.close();
+ }
+
+ /**
+ * Test builder with all options set
+ */
+ public void testBuilderWithAllOptions() {
+ // GIVEN
+ String expectedEventsEndpoint = "https://custom.example.com/events";
+ String expectedPeopleEndpoint = "https://custom.example.com/people";
+ String expectedGroupsEndpoint = "https://custom.example.com/groups";
+ String expectedImportEndpoint = "https://custom.example.com/import";
+ boolean expectedGzipCompression = true;
+ LocalFlagsConfig expectedLocalFlagsConfig =
+ new LocalFlagsConfig.Builder().build();
+ OrgJsonSerializer expectedJsonSerializer = new OrgJsonSerializer();
+
+ // WHEN
+ MixpanelAPI api = new MixpanelAPI.Builder()
+ .eventsEndpoint(expectedEventsEndpoint)
+ .peopleEndpoint(expectedPeopleEndpoint)
+ .groupsEndpoint(expectedGroupsEndpoint)
+ .importEndpoint(expectedImportEndpoint)
+ .useGzipCompression(expectedGzipCompression)
+ .flagsConfig(expectedLocalFlagsConfig)
+ .jsonSerializer(expectedJsonSerializer)
+ .build();
+
+ // THEN
+ assertEquals(expectedEventsEndpoint, api.mEventsEndpoint);
+ assertEquals(expectedPeopleEndpoint, api.mPeopleEndpoint);
+ assertEquals(expectedGroupsEndpoint, api.mGroupsEndpoint);
+ assertEquals(expectedImportEndpoint, api.mImportEndpoint);
+ assertEquals(expectedGzipCompression, api.mUseGzipCompression);
+ assertEquals(expectedJsonSerializer, api.mJsonSerializer);
+ assertNotNull(api.mLocalFlags);
+ assertNull(api.mRemoteFlags);
+ api.close();
+ }
+
+ /**
+ * Test builder with LocalFlagsConfig
+ */
+ public void testBuilderWithLocalFlagsConfig() {
+ LocalFlagsConfig localConfig =
+ new LocalFlagsConfig.Builder().build();
+
+ MixpanelAPI api = new MixpanelAPI.Builder()
+ .flagsConfig(localConfig)
+ .build();
+
+ assertNotNull(api.mLocalFlags);
+ assertNull(api.mRemoteFlags);
+ api.close();
+ }
+
+ /**
+ * Test builder with RemoteFlagsConfig
+ */
+ public void testBuilderWithRemoteFlagsConfig() {
+ RemoteFlagsConfig remoteConfig =
+ new RemoteFlagsConfig.Builder().build();
+
+ MixpanelAPI api = new MixpanelAPI.Builder()
+ .flagsConfig(remoteConfig)
+ .build();
+
+ assertNull(api.mLocalFlags);
+ assertNotNull(api.mRemoteFlags);
+ api.close();
+ }
+
+ /**
+ * Test default constructor with no arguments
+ */
+ public void testConstructorNoArgs() {
+ // WHEN
+ MixpanelAPI api = new MixpanelAPI();
+
+ // THEN
+ assertEquals(Config.BASE_ENDPOINT + "/track", api.mEventsEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/engage", api.mPeopleEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/groups", api.mGroupsEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/import", api.mImportEndpoint);
+ assertFalse(api.mUseGzipCompression);
+ assertNull(api.mLocalFlags);
+ assertNull(api.mRemoteFlags);
+ api.close();
+ }
+
+ /**
+ * Test constructor with gzip compression parameter
+ */
+ public void testConstructorWithGzipCompression() {
+ // GIVEN
+ boolean expectedGzipCompression = true;
+
+ // WHEN
+ MixpanelAPI api = new MixpanelAPI(expectedGzipCompression);
+
+ // THEN
+ assertEquals(Config.BASE_ENDPOINT + "/track", api.mEventsEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/engage", api.mPeopleEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/groups", api.mGroupsEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/import", api.mImportEndpoint);
+ assertEquals(expectedGzipCompression, api.mUseGzipCompression);
+ assertNull(api.mLocalFlags);
+ assertNull(api.mRemoteFlags);
+ api.close();
+ }
+
+ /**
+ * Test constructor with LocalFlagsConfig
+ */
+ public void testConstructorWithLocalFlagsConfig() {
+ // GIVEN
+ LocalFlagsConfig expectedLocalFlagsConfig =
+ new LocalFlagsConfig.Builder().build();
+
+ // WHEN
+ MixpanelAPI api = new MixpanelAPI(expectedLocalFlagsConfig);
+
+ // THEN
+ assertEquals(Config.BASE_ENDPOINT + "/track", api.mEventsEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/engage", api.mPeopleEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/groups", api.mGroupsEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/import", api.mImportEndpoint);
+ assertFalse(api.mUseGzipCompression);
+ assertNotNull(api.mLocalFlags);
+ assertNull(api.mRemoteFlags);
+ api.close();
+ }
+
+ /**
+ * Test constructor with RemoteFlagsConfig
+ */
+ public void testConstructorWithRemoteFlagsConfig() {
+ // GIVEN
+ RemoteFlagsConfig expectedRemoteFlagsConfig = RemoteFlagsConfig.builder().build();
+
+ // WHEN
+ MixpanelAPI api = new MixpanelAPI(expectedRemoteFlagsConfig);
+
+ // THEN
+ assertEquals(Config.BASE_ENDPOINT + "/track", api.mEventsEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/engage", api.mPeopleEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/groups", api.mGroupsEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/import", api.mImportEndpoint);
+ assertFalse(api.mUseGzipCompression);
+ assertNull(api.mLocalFlags);
+ assertNotNull(api.mRemoteFlags);
+ api.close();
+ }
+
+ /**
+ * Test constructor with custom events and people endpoints
+ */
+ public void testConstructorWithTwoEndpoints() {
+ // GIVEN
+ String expectedEventsEndpoint = "https://custom.example.com/events";
+ String expectedPeopleEndpoint = "https://custom.example.com/people";
+
+ // WHEN
+ MixpanelAPI api = new MixpanelAPI(expectedEventsEndpoint, expectedPeopleEndpoint);
+
+ // THEN
+ assertEquals(expectedEventsEndpoint, api.mEventsEndpoint);
+ assertEquals(expectedPeopleEndpoint, api.mPeopleEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/groups", api.mGroupsEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/import", api.mImportEndpoint);
+ assertFalse(api.mUseGzipCompression);
+ assertNull(api.mLocalFlags);
+ assertNull(api.mRemoteFlags);
+ api.close();
+ }
+
+ /**
+ * Test constructor with custom events, people, and groups endpoints
+ */
+ public void testConstructorWithThreeEndpoints() {
+ // GIVEN
+ String expectedEventsEndpoint = "https://custom.example.com/events";
+ String expectedPeopleEndpoint = "https://custom.example.com/people";
+ String expectedGroupsEndpoint = "https://custom.example.com/groups";
+
+ // WHEN
+ MixpanelAPI api = new MixpanelAPI(
+ expectedEventsEndpoint,
+ expectedPeopleEndpoint,
+ expectedGroupsEndpoint
+ );
+
+ // THEN
+ assertEquals(expectedEventsEndpoint, api.mEventsEndpoint);
+ assertEquals(expectedPeopleEndpoint, api.mPeopleEndpoint);
+ assertEquals(expectedGroupsEndpoint, api.mGroupsEndpoint);
+ assertEquals(Config.BASE_ENDPOINT + "/import", api.mImportEndpoint);
+ assertFalse(api.mUseGzipCompression);
+ assertNull(api.mLocalFlags);
+ assertNull(api.mRemoteFlags);
+ api.close();
+ }
+
+ /**
+ * Test constructor with custom events, people, groups, and import endpoints
+ */
+ public void testConstructorWithFourEndpoints() {
+ // GIVEN
+ String expectedEventsEndpoint = "https://custom.example.com/events";
+ String expectedPeopleEndpoint = "https://custom.example.com/people";
+ String expectedGroupsEndpoint = "https://custom.example.com/groups";
+ String expectedImportEndpoint = "https://custom.example.com/import";
+
+ // WHEN
+ MixpanelAPI api = new MixpanelAPI(
+ expectedEventsEndpoint,
+ expectedPeopleEndpoint,
+ expectedGroupsEndpoint,
+ expectedImportEndpoint
+ );
+
+ // THEN
+ assertEquals(expectedEventsEndpoint, api.mEventsEndpoint);
+ assertEquals(expectedPeopleEndpoint, api.mPeopleEndpoint);
+ assertEquals(expectedGroupsEndpoint, api.mGroupsEndpoint);
+ assertEquals(expectedImportEndpoint, api.mImportEndpoint);
+ assertFalse(api.mUseGzipCompression);
+ assertNull(api.mLocalFlags);
+ assertNull(api.mRemoteFlags);
+ api.close();
+ }
+
+ /**
+ * Test constructor with all four endpoints and gzip compression
+ */
+ public void testConstructorWithFourEndpointsAndGzip() {
+ // GIVEN
+ String expectedEventsEndpoint = "https://custom.example.com/events";
+ String expectedPeopleEndpoint = "https://custom.example.com/people";
+ String expectedGroupsEndpoint = "https://custom.example.com/groups";
+ String expectedImportEndpoint = "https://custom.example.com/import";
+ boolean expectedGzipCompression = true;
+
+ // WHEN
+ MixpanelAPI api = new MixpanelAPI(
+ expectedEventsEndpoint,
+ expectedPeopleEndpoint,
+ expectedGroupsEndpoint,
+ expectedImportEndpoint,
+ expectedGzipCompression
+ );
+
+ // THEN
+ assertEquals(expectedEventsEndpoint, api.mEventsEndpoint);
+ assertEquals(expectedPeopleEndpoint, api.mPeopleEndpoint);
+ assertEquals(expectedGroupsEndpoint, api.mGroupsEndpoint);
+ assertEquals(expectedImportEndpoint, api.mImportEndpoint);
+ assertEquals(expectedGzipCompression, api.mUseGzipCompression);
+ assertNull(api.mLocalFlags);
+ assertNull(api.mRemoteFlags);
+ api.close();
+ }
}
diff --git a/src/test/java/com/mixpanel/mixpanelapi/internal/JsonSerializerTest.java b/src/test/java/com/mixpanel/mixpanelapi/internal/JsonSerializerTest.java
index 4feccd3..9b09511 100644
--- a/src/test/java/com/mixpanel/mixpanelapi/internal/JsonSerializerTest.java
+++ b/src/test/java/com/mixpanel/mixpanelapi/internal/JsonSerializerTest.java
@@ -20,9 +20,6 @@ public void testOrgJsonSerializerEmptyList() throws IOException {
String result = serializer.serializeArray(messages);
assertEquals("[]", result);
-
- byte[] bytes = serializer.serializeArrayToBytes(messages);
- assertEquals("[]", new String(bytes, "UTF-8"));
}
public void testOrgJsonSerializerSingleMessage() throws IOException {
@@ -105,129 +102,9 @@ public void testOrgJsonSerializerComplexObject() throws IOException {
assertEquals(true, parsedInnerArray.getBoolean(2));
}
- public void testOrgJsonSerializerImplementationName() {
- JsonSerializer serializer = new OrgJsonSerializer();
- assertEquals("org.json", serializer.getImplementationName());
- }
-
- public void testJacksonSerializerIfAvailable() throws IOException {
- // This test will only run if Jackson is on the classpath
- boolean jacksonAvailable = false;
- try {
- Class.forName("com.fasterxml.jackson.core.JsonFactory");
- jacksonAvailable = true;
- } catch (ClassNotFoundException e) {
- // Jackson not available, skip Jackson-specific tests
- }
-
- if (jacksonAvailable) {
- JsonSerializer serializer = new JacksonSerializer();
-
- // Test empty list
- List messages = new ArrayList<>();
- String result = serializer.serializeArray(messages);
- assertEquals("[]", result);
-
- // Test single message
- JSONObject message = new JSONObject();
- message.put("event", "jackson_test");
- message.put("value", 123);
- messages = Arrays.asList(message);
-
- result = serializer.serializeArray(messages);
- JSONArray array = new JSONArray(result);
- assertEquals(1, array.length());
- JSONObject parsed = array.getJSONObject(0);
- assertEquals("jackson_test", parsed.getString("event"));
- assertEquals(123, parsed.getInt("value"));
-
- // Test implementation name
- assertEquals("Jackson", serializer.getImplementationName());
- }
- }
-
- public void testJacksonSerializerComplexObjectIfAvailable() throws IOException {
- // This test will only run if Jackson is on the classpath
- boolean jacksonAvailable = false;
- try {
- Class.forName("com.fasterxml.jackson.core.JsonFactory");
- jacksonAvailable = true;
- } catch (ClassNotFoundException e) {
- // Jackson not available, skip Jackson-specific tests
- }
-
- if (jacksonAvailable) {
- JsonSerializer serializer = new JacksonSerializer();
-
- JSONObject message = new JSONObject();
- message.put("event", "complex_jackson_event");
- message.put("null_value", JSONObject.NULL);
- message.put("boolean_value", false);
- message.put("int_value", 42);
- message.put("long_value", 9999999999L);
- message.put("double_value", 3.14159);
- message.put("float_value", 2.5f);
- message.put("string_value", "test with \"quotes\" and special chars: \n\t");
-
- JSONObject nested = new JSONObject();
- nested.put("level2", new JSONObject().put("level3", "deep value"));
- message.put("nested", nested);
-
- JSONArray array = new JSONArray();
- array.put("string");
- array.put(100);
- array.put(false);
- array.put(JSONObject.NULL);
- array.put(new JSONObject().put("in_array", true));
- message.put("array", array);
-
- List messages = Arrays.asList(message);
- String result = serializer.serializeArray(messages);
-
- // Verify the result can be parsed back correctly
- JSONArray parsedArray = new JSONArray(result);
- JSONObject parsed = parsedArray.getJSONObject(0);
-
- assertEquals("complex_jackson_event", parsed.getString("event"));
- assertTrue(parsed.isNull("null_value"));
- assertEquals(false, parsed.getBoolean("boolean_value"));
- assertEquals(42, parsed.getInt("int_value"));
- assertEquals(9999999999L, parsed.getLong("long_value"));
- assertEquals(3.14159, parsed.getDouble("double_value"), 0.00001);
- assertEquals(2.5f, parsed.getFloat("float_value"), 0.01);
- assertEquals("test with \"quotes\" and special chars: \n\t", parsed.getString("string_value"));
-
- assertEquals("deep value",
- parsed.getJSONObject("nested")
- .getJSONObject("level2")
- .getString("level3"));
-
- JSONArray parsedInnerArray = parsed.getJSONArray("array");
- assertEquals(5, parsedInnerArray.length());
- assertEquals("string", parsedInnerArray.getString(0));
- assertEquals(100, parsedInnerArray.getInt(1));
- assertEquals(false, parsedInnerArray.getBoolean(2));
- assertTrue(parsedInnerArray.isNull(3));
- assertEquals(true, parsedInnerArray.getJSONObject(4).getBoolean("in_array"));
- }
- }
-
- public void testSerializerFactoryReturnsCorrectImplementation() {
- JsonSerializer serializer = SerializerFactory.getInstance();
- assertNotNull(serializer);
-
- // Check that we get a valid implementation
- String implName = serializer.getImplementationName();
- assertTrue("org.json".equals(implName) || "Jackson".equals(implName));
-
- // Verify it's the same instance on subsequent calls (singleton)
- JsonSerializer serializer2 = SerializerFactory.getInstance();
- assertSame(serializer, serializer2);
- }
-
public void testLargeBatchSerialization() throws IOException {
// Test with a large batch to verify performance doesn't degrade
- JsonSerializer serializer = SerializerFactory.getInstance();
+ JsonSerializer serializer = new OrgJsonSerializer();
List messages = new ArrayList<>();
// Create 2000 messages (max batch size for /import)
@@ -259,6 +136,6 @@ public void testLargeBatchSerialization() throws IOException {
// Log serialization time for reference
System.out.println("Serialized 2000 messages in " + (endTime - startTime) +
- "ms using " + serializer.getImplementationName());
+ "ms using " + serializer.getClass().getName());
}
}
\ No newline at end of file
diff --git a/src/test/java/com/mixpanel/mixpanelapi/internal/SerializerBenchmark.java b/src/test/java/com/mixpanel/mixpanelapi/internal/SerializerBenchmark.java
deleted file mode 100644
index 7c9040d..0000000
--- a/src/test/java/com/mixpanel/mixpanelapi/internal/SerializerBenchmark.java
+++ /dev/null
@@ -1,204 +0,0 @@
-package com.mixpanel.mixpanelapi.internal;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Performance benchmark for comparing JSON serialization implementations.
- * Run this class directly to see performance comparisons.
- */
-public class SerializerBenchmark {
-
- private static final int WARMUP_ITERATIONS = 100;
- private static final int BENCHMARK_ITERATIONS = 1000;
- private static final int[] MESSAGE_COUNTS = {1, 10, 50, 100, 500, 1000, 2000};
-
- public static void main(String[] args) {
- System.out.println("JSON Serializer Performance Benchmark");
- System.out.println("=====================================\n");
-
- // Check if Jackson is available
- boolean jacksonAvailable = false;
- try {
- Class.forName("com.fasterxml.jackson.core.JsonFactory");
- jacksonAvailable = true;
- System.out.println("✓ Jackson is available on classpath");
- } catch (ClassNotFoundException e) {
- System.out.println("✗ Jackson is NOT available on classpath");
- System.out.println(" Add jackson-databind dependency to enable high-performance serialization\n");
- }
-
- // Create serializers
- JsonSerializer orgJsonSerializer = new OrgJsonSerializer();
- JsonSerializer jacksonSerializer = null;
- if (jacksonAvailable) {
- try {
- jacksonSerializer = new JacksonSerializer();
- } catch (NoClassDefFoundError e) {
- System.out.println("Failed to initialize Jackson serializer");
- jacksonAvailable = false;
- }
- }
-
- System.out.println("\nRunning benchmarks...\n");
-
- // Run benchmarks for different message counts
- for (int messageCount : MESSAGE_COUNTS) {
- System.out.println("Testing with " + messageCount + " messages:");
-
- List messages = createTestMessages(messageCount);
-
- // Warmup
- warmup(orgJsonSerializer, messages);
- if (jacksonAvailable) {
- warmup(jacksonSerializer, messages);
- }
-
- // Benchmark org.json
- long orgJsonTime = benchmark(orgJsonSerializer, messages);
- System.out.printf(" org.json: %,d ms (%.2f ms/msg)\n",
- orgJsonTime, (double) orgJsonTime / messageCount);
-
- // Benchmark Jackson if available
- if (jacksonAvailable) {
- long jacksonTime = benchmark(jacksonSerializer, messages);
- System.out.printf(" Jackson: %,d ms (%.2f ms/msg)\n",
- jacksonTime, (double) jacksonTime / messageCount);
-
- // Calculate improvement
- double improvement = (double) orgJsonTime / jacksonTime;
- System.out.printf(" Speedup: %.2fx faster\n", improvement);
- }
-
- System.out.println();
- }
-
- // Memory usage comparison for large batch
- System.out.println("Memory Usage Test (2000 messages):");
- List largeMessages = createTestMessages(2000);
-
- Runtime runtime = Runtime.getRuntime();
- System.gc();
- long beforeMemory = runtime.totalMemory() - runtime.freeMemory();
-
- // Test org.json memory usage
- try {
- for (int i = 0; i < 100; i++) {
- orgJsonSerializer.serializeArray(largeMessages);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- System.gc();
- long afterOrgJson = runtime.totalMemory() - runtime.freeMemory();
- long orgJsonMemory = afterOrgJson - beforeMemory;
- System.out.printf(" org.json memory usage: %,d bytes\n", orgJsonMemory);
-
- if (jacksonAvailable) {
- System.gc();
- beforeMemory = runtime.totalMemory() - runtime.freeMemory();
-
- try {
- for (int i = 0; i < 100; i++) {
- jacksonSerializer.serializeArray(largeMessages);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- System.gc();
- long afterJackson = runtime.totalMemory() - runtime.freeMemory();
- long jacksonMemory = afterJackson - beforeMemory;
- System.out.printf(" Jackson memory usage: %,d bytes\n", jacksonMemory);
- System.out.printf(" Memory savings: %,d bytes (%.1f%%)\n",
- orgJsonMemory - jacksonMemory,
- ((double)(orgJsonMemory - jacksonMemory) / orgJsonMemory) * 100);
- }
-
- System.out.println("\nBenchmark complete!");
- System.out.println("\nRecommendation:");
- if (jacksonAvailable) {
- System.out.println("✓ Jackson is providing significant performance improvements.");
- System.out.println(" The library will automatically use Jackson for JSON serialization.");
- } else {
- System.out.println("⚠ Consider adding Jackson dependency for better performance:");
- System.out.println(" ");
- System.out.println(" com.fasterxml.jackson.core");
- System.out.println(" jackson-databind");
- System.out.println(" 2.20.0");
- System.out.println(" ");
- }
- }
-
- private static List createTestMessages(int count) {
- List messages = new ArrayList<>(count);
- long timestamp = System.currentTimeMillis();
-
- for (int i = 0; i < count; i++) {
- JSONObject message = new JSONObject();
- message.put("event", "test_event_" + i);
- message.put("$insert_id", "id_" + timestamp + "_" + i);
- message.put("time", timestamp - ((long) i * 1000));
-
- JSONObject properties = new JSONObject();
- properties.put("$token", "test_token_12345");
- properties.put("distinct_id", "user_" + (i % 100));
- properties.put("mp_lib", "java");
- properties.put("$lib_version", "1.6.0");
- properties.put("index", i);
- properties.put("batch_size", count);
- properties.put("test_string", "This is a test string with some content to make it more realistic");
- properties.put("test_number", Math.random() * 1000);
- properties.put("test_boolean", i % 2 == 0);
-
- // Add nested object
- JSONObject nested = new JSONObject();
- nested.put("nested_value", "value_" + i);
- nested.put("nested_number", i * 10);
- properties.put("nested_object", nested);
-
- // Add array
- JSONArray array = new JSONArray();
- for (int j = 0; j < 5; j++) {
- array.put("item_" + j);
- }
- properties.put("test_array", array);
-
- message.put("properties", properties);
- messages.add(message);
- }
-
- return messages;
- }
-
- private static void warmup(JsonSerializer serializer, List messages) {
- try {
- for (int i = 0; i < WARMUP_ITERATIONS; i++) {
- serializer.serializeArray(messages);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- private static long benchmark(JsonSerializer serializer, List messages) {
- long startTime = System.nanoTime();
-
- try {
- for (int i = 0; i < BENCHMARK_ITERATIONS; i++) {
- serializer.serializeArray(messages);
- }
- } catch (IOException e) {
- e.printStackTrace();
- return -1;
- }
-
- long endTime = System.nanoTime();
- return (endTime - startTime) / 1_000_000; // Convert to milliseconds
- }
-}
\ No newline at end of file