<commit_before>package org.swellrt.model.generic;

import org.swellrt.model.ReadableBoolean;
import org.swellrt.model.ReadableMap;
import org.swellrt.model.ReadableNumber;
import org.swellrt.model.ReadableTypeVisitor;
import org.swellrt.model.adt.DocumentBasedBasicRMap;
import org.swellrt.model.shared.ModelUtils;
import org.waveprotocol.wave.model.adt.ObservableBasicMap;
import org.waveprotocol.wave.model.document.Doc;
import org.waveprotocol.wave.model.document.ObservableDocument;
import org.waveprotocol.wave.model.document.util.DefaultDocEventRouter;
import org.waveprotocol.wave.model.document.util.DocHelper;
import org.waveprotocol.wave.model.util.CopyOnWriteSet;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.util.Serializer;
import org.waveprotocol.wave.model.wave.SourcesEvents;

import java.util.Collections;
import java.util.Set;

public class MapType extends Type implements ReadableMap, SourcesEvents<MapType.Listener> {


  /**
   * Listener to map events.
   */
  public interface Listener {

    void onValueChanged(String key, Type oldValue, Type newValue);

    void onValueRemoved(String key, Type value);

  }


  /**
   * Get an instance of MapType within the model backed by a document. This
   * method is used for deserialization.
   *
   * @param model
   * @param substrateDocumentId
   * @return
   */
  protected static MapType deserialize(Type parent, String substrateDocumentId) {
    Preconditions.checkArgument(substrateDocumentId.startsWith(PREFIX),
        "Not a document id for MapType");
    MapType map = new MapType(parent.getModel());
    map.attach(parent, substrateDocumentId);
    return map;
  }

  protected static MapType deserialize(Model model, String substrateDocumentId) {
    Preconditions.checkArgument(substrateDocumentId.startsWith(PREFIX),
        "Not a document id for MapType");
    MapType map = new MapType(model);
    map.attach(null, substrateDocumentId);
    return map;
  }


  public final static String TYPE_NAME = "MapType";
  public final static String PREFIX = "map";

  public final static String TAG_MAP = "map";
  public final static String TAG_ENTRY = "entry";
  public final static String KEY_ATTR_NAME = "k";
  public final static String VALUE_ATTR_NAME = "v";

  private ObservableBasicMap<String, Type> observableMap;
  private ObservableBasicMap.Listener<String, Type> observableMapListener;

  private Model model;

  private ObservableDocument backendDocument;
  private String backendDocumentId;

  private MetadataContainer metadata;
  private ValuesContainer values;

  private Doc.E backendMapElement;

  private boolean isAttached;

  private final CopyOnWriteSet<Listener> listeners = CopyOnWriteSet.create();

  private DefaultDocEventRouter router;

  /**
   * Constructor for MapType instances.
   *
   * @param model The model metadata object where this instance belongs to.
   */
  protected MapType(Model model) {

    this.model = model;
    this.isAttached = false;

    observableMapListener = new ObservableBasicMap.Listener<String, Type>() {

      @Override
      public void onEntrySet(final String key, final Type oldValue, final Type newValue) {

        if (newValue == null) {

          for (Listener l : listeners)
            l.onValueRemoved(key, oldValue);


        } else {

          // Under some circunstances, value's update will reach after map entry
          // update so the new value is not attached.
          // To avoid the issue, we wait for the value's update and then
          // trigger again the event.

          if (newValue.isAttached()) {

            for (Listener l : listeners)
              l.onValueChanged(key, oldValue, newValue);

          } else {

            final Integer index = newValue.getValueRefefence();
            if (index != null)
              values.registerEventHandler(index, new ValuesContainer.EventHandler() {

                @Override
                public void run(String value) {

                  newValue.attach(MapType.this, "" + index.intValue());

                  for (Listener l : listeners)
                    l.onValueChanged(key, oldValue, newValue);

                }
              });
            }
          }
        }
    };
  }

  //
  // Type interface
  //

  @Override
  protected void attach(Type parent) {
    Preconditions.checkArgument(!isAttached, "Already attached map type");
    String substrateDocumentId = model.generateDocId(getPrefix());
    attach(parent, substrateDocumentId);
  }

  @Override
  protected void attach(Type parent, int slotIndex) {
    throw new IllegalStateException("This method is not allowed for a MapType");
  }

  @Override
  protected void attach(Type parent, String substrateDocumentId) {
    Preconditions.checkArgument(!isAttached, "Already attached map type");
    Preconditions.checkNotNull(substrateDocumentId, "Document id is null");

    backendDocumentId = substrateDocumentId;

    boolean isNew = false;

    // Be careful with order of following steps!

    // Get or create substrate document
    if (!model.getModelDocuments().contains(backendDocumentId)) {
      backendDocument = model.createDocument(backendDocumentId);
      isNew = true;
    } else {
      backendDocument = model.getDocument(backendDocumentId);
    }

    // To debug doc operations
    /*
    backendDocument.addListener(new DocumentHandler<Doc.N, Doc.E, Doc.T>() {

      @Override
      public void onDocumentEvents(
          org.waveprotocol.wave.model.document.indexed.DocumentHandler.EventBundle<N, E, T> event) {

        for (DocumentEvent<Doc.N, Doc.E, Doc.T> e : event.getEventComponents()) {
          trace("(" + backendDocumentId + ") Doc Event " + e.toString());
        }

      }

    });
    */

    router = DefaultDocEventRouter.create(backendDocument);

    // Metadata section
    metadata = MetadataContainer.get(backendDocument);

    if (isNew) {
      metadata.setCreator(model.getCurrentParticipantId());
    }


    // Map section
    backendMapElement = DocHelper.getElementWithTagName(backendDocument, TAG_MAP);
    if (backendMapElement == null) {
      backendMapElement =
          backendDocument.createChildElement(backendDocument.getDocumentElement(), TAG_MAP,
          Collections.<String, String> emptyMap());
    }

    // Initialize values section. Always before loading the observable map
    this.values = ValuesContainer.get(backendDocument, router, this);

    // Attached! Before the observable list initialization to allow access
    // during initialization
    this.isAttached = true;

    // Initialize observable map
    this.observableMap =
        DocumentBasedBasicRMap.create(router, backendMapElement, Serializer.STRING,
            new MapSerializer(this), TAG_ENTRY, KEY_ATTR_NAME, VALUE_ATTR_NAME);

    this.observableMap.addListener(observableMapListener);
  }

  protected void deattach() {
    Preconditions.checkArgument(isAttached, "Unable to detach an unattached Map");
    metadata.setDetachedPath();
    isAttached = false;
  }

  @Override
  protected String getPrefix() {
    return PREFIX;
  }


  @Override
  protected boolean isAttached() {
    return isAttached;
  }

  @Override
  protected String serialize() {
    Preconditions.checkArgument(isAttached, "Unable to serialize an unattached Map");
    return backendDocumentId;
  }

  @Override
  protected ListElementInitializer getListElementInitializer() {
    return new ListElementInitializer() {

      @Override
      public String getType() {
        return PREFIX;
      }

      @Override
      public String getBackendId() {
        return serialize();
      }
    };
  }


  //
  // Listeners
  //


  @Override
  public void addListener(Listener listener) {
    listeners.add(listener);
  }


  @Override
  public void removeListener(Listener listener) {
    listeners.remove(listener);
  }


  //
  // Map operations
  //

  @Override
  public Type get(String key) {
    Preconditions.checkArgument(isAttached, "Unable to get values from an unattached Map");

    if (observableMap.keySet().contains(key)) {
      return observableMap.get(key);
    }

    return null;
  }


  public Type put(String key, Type value) {
    Preconditions.checkArgument(isAttached, "Unable to put values into an unattached Map");
    Preconditions.checkArgument(!value.isAttached(),
        "Already attached Type instances can't be put into a Map");

    Type oldValue = observableMap.get(key);

    // If replacing primitive values, reuse the old value's container slot
    if (oldValue != null && ModelUtils.isPrimitiveType(oldValue)
        && ModelUtils.isPrimitiveType(value)) {

      /* TODO consider to update just the value if old a new types are similar
      boolean isPrimitiveReplace = ModelUtils.isPrimitiveType(oldValue) && ModelUtils.isPrimitiveType(value);
      if (isPrimitiveReplace && (oldValue instanceof StringType) && (value instanceof StringType)) {
        ...
      } */

      value.attach(this, oldValue.getValueRefefence());
    } else {
      // Attach to a new substrate document or to a values container
      value.attach(this);
    }

    // This should be always a new put, otherwise the !value.isAttached
    // precondition
    // would be false
    if (!observableMap.put(key, value)) {
      value.deattach();
      return null;
    }

    value = observableMap.get(key);
    value.setPath(getPath() + "." + key);


    if (oldValue != null) {
      oldValue.deattach();
    }

    return value;
  }



  public StringType put(String key, String value) {
    Preconditions.checkArgument(isAttached, "Unable to put values into an unattached Map");

    StringType strValue = new StringType(value);
    put(key, strValue);

    return strValue;
  }

  @Override
  public Set<String> keySet() {
    return observableMap.keySet();
  }

  @Override
  public boolean hasKey(String key) {
    return observableMap.keySet().contains(key);
  }


  public void remove(String key) {
    Preconditions.checkArgument(isAttached, "Unable to remove values from an unattached Map");
    Type removedValue = observableMap.get(key);
    if (removedValue != null) {
      observableMap.remove(key);
      removedValue.deattach();
    }

  }

  @Override
  public String getDocumentId() {
    return backendDocumentId;
  }

  @Override
  public String getType() {
    return TYPE_NAME;
  }

  @Override
  public String getPath() {
    return metadata.getPath();<commit_msg>Revert "Removes an useless hash map caching observable map substrate values"<commit_after>package org.swellrt.model.generic;

import org.swellrt.model.ReadableBoolean;
import org.swellrt.model.ReadableMap;
import org.swellrt.model.ReadableNumber;
import org.swellrt.model.ReadableTypeVisitor;
import org.swellrt.model.adt.DocumentBasedBasicRMap;
import org.swellrt.model.shared.ModelUtils;
import org.waveprotocol.wave.model.adt.ObservableBasicMap;
import org.waveprotocol.wave.model.document.Doc;
import org.waveprotocol.wave.model.document.ObservableDocument;
import org.waveprotocol.wave.model.document.util.DefaultDocEventRouter;
import org.waveprotocol.wave.model.document.util.DocHelper;
import org.waveprotocol.wave.model.util.CopyOnWriteSet;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.util.Serializer;
import org.waveprotocol.wave.model.wave.SourcesEvents;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapType extends Type implements ReadableMap, SourcesEvents<MapType.Listener> {


  /**
   * Listener to map events.
   */
  public interface Listener {

    void onValueChanged(String key, Type oldValue, Type newValue);

    void onValueRemoved(String key, Type value);

  }


  /**
   * Get an instance of MapType within the model backed by a document. This
   * method is used for deserialization.
   *
   * @param model
   * @param substrateDocumentId
   * @return
   */
  protected static MapType deserialize(Type parent, String substrateDocumentId) {
    Preconditions.checkArgument(substrateDocumentId.startsWith(PREFIX),
        "Not a document id for MapType");
    MapType map = new MapType(parent.getModel());
    map.attach(parent, substrateDocumentId);
    return map;
  }

  protected static MapType deserialize(Model model, String substrateDocumentId) {
    Preconditions.checkArgument(substrateDocumentId.startsWith(PREFIX),
        "Not a document id for MapType");
    MapType map = new MapType(model);
    map.attach(null, substrateDocumentId);
    return map;
  }


  public final static String TYPE_NAME = "MapType";
  public final static String PREFIX = "map";

  public final static String TAG_MAP = "map";
  public final static String TAG_ENTRY = "entry";
  public final static String KEY_ATTR_NAME = "k";
  public final static String VALUE_ATTR_NAME = "v";

  private ObservableBasicMap<String, Type> observableMap;
  private ObservableBasicMap.Listener<String, Type> observableMapListener;

  private Model model;

  private ObservableDocument backendDocument;
  private String backendDocumentId;

  private MetadataContainer metadata;
  private ValuesContainer values;

  private Doc.E backendMapElement;

  private boolean isAttached;

  private final CopyOnWriteSet<Listener> listeners = CopyOnWriteSet.create();

  private DefaultDocEventRouter router;

  private final Map<String, Type> cachedMap = new HashMap<String, Type>();

  /**
   * Constructor for MapType instances.
   *
   * @param model The model metadata object where this instance belongs to.
   */
  protected MapType(Model model) {

    this.model = model;
    this.isAttached = false;

    observableMapListener = new ObservableBasicMap.Listener<String, Type>() {

      @Override
      public void onEntrySet(final String key, final Type oldValue, final Type newValue) {

        if (newValue == null) {

          for (Listener l : listeners)
            l.onValueRemoved(key, oldValue);


        } else {

          // Under some circunstances, value's update will reach after map entry
          // update so the new value is not attached.
          // To avoid the issue, we wait for the value's update and then
          // trigger again the event.

          if (newValue.isAttached()) {

            for (Listener l : listeners)
              l.onValueChanged(key, oldValue, newValue);

          } else {

            final Integer index = newValue.getValueRefefence();
            if (index != null)
              values.registerEventHandler(index, new ValuesContainer.EventHandler() {

                @Override
                public void run(String value) {

                  newValue.attach(MapType.this, "" + index.intValue());

                  for (Listener l : listeners)
                    l.onValueChanged(key, oldValue, newValue);

                }
              });
            }
          }
        }
    };
  }

  //
  // Type interface
  //

  @Override
  protected void attach(Type parent) {
    Preconditions.checkArgument(!isAttached, "Already attached map type");
    String substrateDocumentId = model.generateDocId(getPrefix());
    attach(parent, substrateDocumentId);
  }

  @Override
  protected void attach(Type parent, int slotIndex) {
    throw new IllegalStateException("This method is not allowed for a MapType");
  }

  @Override
  protected void attach(Type parent, String substrateDocumentId) {
    Preconditions.checkArgument(!isAttached, "Already attached map type");
    Preconditions.checkNotNull(substrateDocumentId, "Document id is null");

    backendDocumentId = substrateDocumentId;

    boolean isNew = false;

    // Be careful with order of following steps!

    // Get or create substrate document
    if (!model.getModelDocuments().contains(backendDocumentId)) {
      backendDocument = model.createDocument(backendDocumentId);
      isNew = true;
    } else {
      backendDocument = model.getDocument(backendDocumentId);
    }

    // To debug doc operations
    /*
    backendDocument.addListener(new DocumentHandler<Doc.N, Doc.E, Doc.T>() {

      @Override
      public void onDocumentEvents(
          org.waveprotocol.wave.model.document.indexed.DocumentHandler.EventBundle<N, E, T> event) {

        for (DocumentEvent<Doc.N, Doc.E, Doc.T> e : event.getEventComponents()) {
          trace("(" + backendDocumentId + ") Doc Event " + e.toString());
        }

      }

    });
    */

    router = DefaultDocEventRouter.create(backendDocument);

    // Metadata section
    metadata = MetadataContainer.get(backendDocument);

    if (isNew) {
      metadata.setCreator(model.getCurrentParticipantId());
    }


    // Map section
    backendMapElement = DocHelper.getElementWithTagName(backendDocument, TAG_MAP);
    if (backendMapElement == null) {
      backendMapElement =
          backendDocument.createChildElement(backendDocument.getDocumentElement(), TAG_MAP,
          Collections.<String, String> emptyMap());
    }

    // Initialize values section. Always before loading the observable map
    this.values = ValuesContainer.get(backendDocument, router, this);

    // Attached! Before the observable list initialization to allow access
    // during initialization
    this.isAttached = true;

    // Initialize observable map
    this.observableMap =
        DocumentBasedBasicRMap.create(router, backendMapElement, Serializer.STRING,
            new MapSerializer(this), TAG_ENTRY, KEY_ATTR_NAME, VALUE_ATTR_NAME);

    this.observableMap.addListener(observableMapListener);
  }

  protected void deattach() {
    Preconditions.checkArgument(isAttached, "Unable to detach an unattached Map");
    metadata.setDetachedPath();
    isAttached = false;
  }

  @Override
  protected String getPrefix() {
    return PREFIX;
  }


  @Override
  protected boolean isAttached() {
    return isAttached;
  }

  @Override
  protected String serialize() {
    Preconditions.checkArgument(isAttached, "Unable to serialize an unattached Map");
    return backendDocumentId;
  }

  @Override
  protected ListElementInitializer getListElementInitializer() {
    return new ListElementInitializer() {

      @Override
      public String getType() {
        return PREFIX;
      }

      @Override
      public String getBackendId() {
        return serialize();
      }
    };
  }


  //
  // Listeners
  //


  @Override
  public void addListener(Listener listener) {
    listeners.add(listener);
  }


  @Override
  public void removeListener(Listener listener) {
    listeners.remove(listener);
  }


  //
  // Map operations
  //

  @Override
  public Type get(String key) {
    Preconditions.checkArgument(isAttached, "Unable to get values from an unattached Map");

    if (observableMap.keySet().contains(key)) {

      if (!cachedMap.containsKey(key)) cachedMap.put(key, observableMap.get(key));

      return cachedMap.get(key);
    }

    return null;
  }


  public Type put(String key, Type value) {
    Preconditions.checkArgument(isAttached, "Unable to put values into an unattached Map");
    Preconditions.checkArgument(!value.isAttached(),
        "Already attached Type instances can't be put into a Map");

    Type oldValue = observableMap.get(key);

    // If replacing primitive values, reuse the old value's container slot
    if (oldValue != null && ModelUtils.isPrimitiveType(oldValue)
        && ModelUtils.isPrimitiveType(value)) {

      /* TODO consider to update just the value if old a new types are similar
      boolean isPrimitiveReplace = ModelUtils.isPrimitiveType(oldValue) && ModelUtils.isPrimitiveType(value);
      if (isPrimitiveReplace && (oldValue instanceof StringType) && (value instanceof StringType)) {
        ...
      } */

      value.attach(this, oldValue.getValueRefefence());
    } else {
      // Attach to a new substrate document or to a values container
      value.attach(this);
    }

    // This should be always a new put, otherwise the !value.isAttached
    // precondition
    // would be false
    if (!observableMap.put(key, value)) {
      value.deattach();
      return null;
    }

    value = observableMap.get(key);
    value.setPath(getPath() + "." + key);

    cachedMap.put(key, value);


    if (oldValue != null) {
      oldValue.deattach();
    }

    return value;
  }



  public StringType put(String key, String value) {
    Preconditions.checkArgument(isAttached, "Unable to put values into an unattached Map");

    StringType strValue = new StringType(value);
    put(key, strValue);

    return strValue;
  }

  @Override
  public Set<String> keySet() {
    return observableMap.keySet();
  }

  @Override
  public boolean hasKey(String key) {
    return observableMap.keySet().contains(key);
  }


  public void remove(String key) {
    Preconditions.checkArgument(isAttached, "Unable to remove values from an unattached Map");
    Type removedValue = observableMap.get(key);
    if (removedValue != null) {
      cachedMap.remove(key);
      observableMap.remove(key);
      removedValue.deattach();
    }

  }

  @Override
  public String getDocumentId() {
    return backendDocumentId;
  }

  @Override
  public String getType() {
    return TYPE_NAME;
  }

  @Override
  public String getPath() {
    return metadata.getPath();
{'fraction_non_alphanumeric': 0.09448855894375793, 'fraction_numerical': 0.0, 'mean_word_length': 3.15703125, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3859037', 'n_tokens_mistral': 6033, 'n_tokens_neox': 5748, 'n_words': 1562}