/*
 * Copyright 2003-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.mps.extapi.model;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;

import java.util.function.BiConsumer;

/**
 * PROVISIONAL API
 *
 * Captures auxiliary model capability to keep primitive attributes. Decouples clients that from specific SModel implementations,
 * facilitates additional non-node values tracked along with a model. SModel implementation MPS uses for regular user models
 * implements this interface
 * <p/>
 * Similar to {@code jetbrains.mps.persistence.PersistenceVersionAware} or {@link GeneratableSModel}.
 * <p/>
 * DESIGN NOTE: We expect attribute keys and values to be serialized and hence limit their kind to String.
 * To facilitate other primitive values, we might overload {@link #setAttribute(String, String)} with proper value type and introduce
 * respective getters (e.g. {@code getIntAttribute()}), if there's need to deal with values other than Strings.
 *
 * Perhaps, with the number of interfaces like this one growing, we shall consider {@code IAdaptable.adapt(Class)} mechanism, with Attribute holder object
 * independent from SModel. What I don't like in the approach with distinct accessor object is that it's harder to control lifecycle and the moment it's
 * accessed. With interface that extends SModel, lifecycle is the same as that of the model.
 *
 * @author Artem Tikhomirov
 * @since 2017.1
 */
public interface ModelWithAttributes extends SModel {
  /**
   * Record extra data with a model
   * @param key attribute identity
   * @param value attribute value, or {@code null} do remove an attribute, if any.
   */
  void setAttribute(@NotNull String key, @Nullable String value);

  /**
   * Retrieve extra model data.
   * @param key attribute identity. There's no penalty if the key is unknown/unsupported with this model implementation
   * @return {@code null} if there's no value for the key.
   */
  @Nullable
  String getAttribute(@NotNull String key);

  /**
   * Iterate over all available attributes. Generally, not present attributes are not reported, however, clients
   * shall expect null values, and implementation may report missing/deleted attributes.
   *
   * For now, the contract is {@code action} shall not modify attributes, we might relax this in future
   * (i.e. iterate over a copy of internal storage), if there are scenarios we find it handy.
   *
   * @param action action to perform for each key-value attribute pair
   */
  void forEach(@NotNull BiConsumer<String, String> action);

  /**
   * @param key attribute identity
   * @param defaultValue attribute value in case implementation doesn't know one.
   * @return value of the attribute, if any, or {@code defaultValue} otherwise
   */
  @Nullable
  default String getAttribute(@NotNull String key, @Nullable String defaultValue) {
    String value = getAttribute(key);
    return value == null ? defaultValue : value;
  }
}
