/*
 * Copyright 2000-2015 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 org.jetbrains.plugins.gradle.model;

import org.gradle.tooling.model.Model;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.tooling.serialization.ToolingSerializer;

import java.io.IOException;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* @author Vladislav.Soroka
*/
public abstract class ModelsHolder<K extends Model,V>  implements Serializable {

  @NotNull private final K myRootModel;
  @NotNull private final Map<String, Object> myModelsById = new LinkedHashMap<String, Object>();
  @Nullable private ToolingSerializer mySerializer;

  public ModelsHolder(@NotNull K rootModel) {
    myRootModel = rootModel;
  }

  public void initToolingSerializer() {
    mySerializer = new ToolingSerializer();
  }

  @NotNull
  public K getRootModel() {
    return myRootModel;
  }

  @Nullable
  public <T> T getExtraProject(Class<T> modelClazz) {
    return getExtraProject(null, modelClazz);
  }

  @Nullable
  public <T> T getExtraProject(@Nullable V model, Class<T> modelClazz) {
    String key = extractMapKey(modelClazz, model);
    Object project = myModelsById.get(key);
    if (project == null) return null;
    if (modelClazz.isInstance(project)) {
      //noinspection unchecked
      return (T)project;
    }
    else {
      return deserialize(project, key, modelClazz);
    }
  }

  @Nullable
  private <T> T deserialize(@NotNull Object data, @NotNull String key, @NotNull Class<T> modelClazz) {
    if (mySerializer == null || !(data instanceof byte[])) {
      myModelsById.remove(key);
      return null;
    }
    String keyPrefix = getModelKeyPrefix(modelClazz);
    deserializeAllDataOfTheType(modelClazz, keyPrefix, myModelsById, mySerializer);
    //noinspection unchecked
    return (T)myModelsById.get(key);
  }

  private static <T> void deserializeAllDataOfTheType(@NotNull Class<T> modelClazz,
                                                      @NotNull String keyPrefix,
                                                      @NotNull Map<String, Object> modelsById,
                                                      @NotNull ToolingSerializer serializer) {
    for (Map.Entry<String, Object> entry : modelsById.entrySet()) {
      String key = entry.getKey();
      if (key.startsWith(keyPrefix)) {
        try {
          T deserializedData = serializer.read((byte[])entry.getValue(), modelClazz);
          if (modelClazz.isInstance(deserializedData)) {
            modelsById.put(key, deserializedData);
          }
          else {
            modelsById.remove(key);
          }
        }
        catch (IllegalArgumentException ignore) {
          // related serialization service was not found
          modelsById.remove(key);
        }
        catch (IOException e) {
          //noinspection UseOfSystemOutOrSystemErr
          System.err.println(e.getMessage());
          modelsById.remove(key);
        }
      }
    }
  }

  public boolean hasModulesWithModel(@NotNull Class modelClazz) {
    String keyPrefix = getModelKeyPrefix(modelClazz);
    for (String key : myModelsById.keySet()) {
      if (key.startsWith(keyPrefix)) return true;
    }
    return false;
  }

  public void addExtraProject(@NotNull Object project, @NotNull Class modelClazz) {
    myModelsById.put(extractMapKey(modelClazz, null), project);
  }

  public void addExtraProject(@NotNull Object project, @NotNull Class modelClazz, @Nullable V subPropject) {
    myModelsById.put(extractMapKey(modelClazz, subPropject), project);
  }

  @NotNull
  protected abstract String getModelKeyPrefix(@NotNull Class modelClazz);

  @NotNull
  protected abstract String extractMapKey(@NotNull Class modelClazz, @Nullable V module);

  @Override
  public String toString() {
    return "Models{" +
           "rootModel=" + myRootModel +
           ", myModelsById=" + myModelsById +
           '}';
  }
}

@@ -894,24 +894,51 @@
 rializable;%0A
+import java.util.Iterator;%0A
 import java.
@@ -2842,16 +2842,25 @@
    for (
+Iterator%3C
 Map.Entr
@@ -2880,16 +2880,20 @@
 ect%3E
- entry :
+%3E iterator =
  mod
@@ -2906,27 +2906,117 @@
 d.entrySet()
-) %7B
+.iterator(); iterator.hasNext(); ) %7B%0A      Map.Entry%3CString, Object%3E entry = iterator.next();
 %0A      Strin
@@ -3327,34 +3327,32 @@
         
-modelsById
+iterator
 .remove(
 key);%0A  
@@ -3335,35 +3335,32 @@
 iterator.remove(
-key
 );%0A          %7D%0A 
@@ -3485,34 +3485,32 @@
         
-modelsById
+iterator
 .remove(
 key);%0A  
@@ -3493,35 +3493,32 @@
 iterator.remove(
-key
 );%0A        %7D%0A   
@@ -3657,26 +3657,24 @@
     
-modelsById
+iterator
 .remove(
 key)
@@ -3661,35 +3661,32 @@
 iterator.remove(
-key
 );%0A        %7D%0A   

fix models map cleanup IDEA-216902