package com.rollbar.payload.utilities;

import com.google.gson.JsonElement;
import com.google.gson.JsonSerializationContext;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

public abstract class Extensible<T extends Extensible<T>> {
    protected Extensible(HashMap<String, Object> members) {
        this.members = members == null ? new HashMap<String, Object>() : new HashMap<String, Object>(members);
    }

    protected abstract Set<String> getKnownMembers();

    private Set<String> knownMembers;

    private Set<String> knownMembers() {
        if (knownMembers == null) {
            knownMembers = getKnownMembers();
        }
        return knownMembers;
    }

    private boolean isKnownMember(String name) {
        return knownMembers().contains(name);
    }

    protected final HashMap<String, Object> members;

    public Object get(String name) {
        return members.getOrDefault(name, null);
    }

    public abstract T copy();

    public T put(String name, Object value) {
        if (isKnownMember(name)) {
            final String msgFmt = "'%s' is a known member and must be set with the corresponding method";
            throw new IllegalArgumentException(String.format(msgFmt, name));
        }
        T returnVal = (T) this.copy(); // Safe because Extensible<T extends Extensible<T>>
        returnVal.members.put(name, value);
        return returnVal;
    }

    public Set<String> keys(boolean withoutKnownMembers) {
        Set<String> keys = new HashSet<String>(members.keySet());
        if (withoutKnownMembers) {
            keys.removeAll(knownMembers());
        }
        return keys;
    }

    JsonElement asJsonElement(JsonSerializationContext jsonSerializationContext) {
        return jsonSerializationContext.serialize(members);
    }
}

@@ -123,37 +123,8 @@
 t;%0A%0A
-import java.util.Collection;%0A
 impo
@@ -198,67 +198,476 @@
 t;%0A%0A
-public abstract class Extensible%3CT extends Extensible%3CT%3E%3E %7B
+/**%0A * Base class for classes that can be extended with arbitrary metadata (as per the%0A * %3Ca href='https://rollbar.com/docs/api/items_post/'%3ERollbar spec%3C/a%3E).%0A * This class, unlike the rest of the classes is mutable. Extra caution is therefore warranted.%0A * @param %3CT%3E The extensible type itself.%0A */%0Apublic abstract class Extensible%3CT extends Extensible%3CT%3E%3E %7B%0A    /**%0A     * Constructor%0A     * @param members the HashMap of all members already in this object%0A     */
 %0A   
@@ -833,32 +833,156 @@
 embers);%0A    %7D%0A%0A
+    /**%0A     * Returns the 'known' keys, that are specially treated by Rollbar%0A     * @return the set of known keys%0A     */%0A
     protected ab
@@ -1338,181 +1338,742 @@
     
-protected final HashMap%3CString, Object%3E members;%0A%0A    public Object get(String name) %7B%0A        return members.getOrDefault(name, null);%0A    %7D%0A%0A    public abstract T copy();%0A
+/**%0A     * The HashMap containing all members.%0A     */%0A    protected final HashMap%3CString, Object%3E members;%0A%0A    /**%0A     * Get the member, or null if not present.%0A     * @param name the member name%0A     * @return null or the member at that key%0A     */%0A    public Object get(String name) %7B%0A        return members.getOrDefault(name, null);%0A    %7D%0A%0A    /**%0A     * Copy this item. Needs to be custom per subclass. Should return the subclass itself (not an Extensible).%0A     * @return An exact copy of this object.%0A     */%0A    public abstract T copy();%0A%0A    /**%0A     * Sets the member. Cannot be used to set known members.%0A     * @param name the member name to set.%0A     * @param value the value to set.%0A     * @return this object.%0A     */
 %0A   
@@ -2112,16 +2112,48 @@
 t value)
+ throws IllegalArgumentException
  %7B%0A     
@@ -2400,12 +2400,8 @@
 al =
- (T)
  thi
@@ -2413,60 +2413,8 @@
 y();
- // Safe because Extensible%3CT extends Extensible%3CT%3E%3E
 %0A   
@@ -2483,24 +2483,181 @@
 Val;%0A    %7D%0A%0A
+    /**%0A     * Get the keys in this extensible%0A     * @param withoutKnownMembers true if you want to leave out known members%0A     * @return the keys%0A     */%0A
     public S

Fix unneeded cast in Extensible, and document it