gt:             Collection<ModuleConfig> configList = configMap.values();
context: /***************************** BEGIN LICENSE BLOCK ***************************
  
  The contents of this file are subject to the Mozilla Public License, v. 2.0.
  If a copy of the MPL was not distributed with this file, You can obtain one
  at http://mozilla.org/MPL/2.0/.
  
  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the License.
   
  Copyright (C) 2012-2015 Sensia Software LLC. All Rights Reserved.
   
  ******************************* END LICENSE BLOCK ***************************/
  
  package org.sensorhub.impl.module;
  
  import java.io.File;
  import java.io.FileReader;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.lang.reflect.Type;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.UUID;
  import org.sensorhub.api.module.IModuleConfigRepository;
  import org.sensorhub.api.module.ModuleConfig;
  import com.google.gson.Gson;
  import com.google.gson.GsonBuilder;
  import com.google.gson.JsonElement;
  import com.google.gson.JsonObject;
  import com.google.gson.JsonPrimitive;
  import com.google.gson.TypeAdapter;
  import com.google.gson.TypeAdapterFactory;
  import com.google.gson.internal.Streams;
  import com.google.gson.reflect.TypeToken;
  import com.google.gson.stream.JsonReader;
  import com.google.gson.stream.JsonWriter;
  
  
  /**
   * <p>
   * Class providing access to the configuration database that is used to
   * persist all modules' configuration.
   * </p>
   *
   * @author Alex Robin <alex.robin@sensiasoftware.com>
   * @since Sep 3, 2013
   */
  public class ModuleConfigJsonFile implements IModuleConfigRepository
  {
      private static final String OBJ_CLASS_FIELD = "objClass";
      
      Map<String, ModuleConfig> configMap;
      Gson gson;
      File configFile;
      
      
      /* GSON type adapter factory for parsing JSON object to a custom subclass.
       * The desired class is indicated by an additional field, whose name is
       * configured by typeFieldName. */
      public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory
      {
          private final Class<?> baseType;
          private final String typeFieldName;
  
  
          public RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName)
          {
              if (typeFieldName == null || baseType == null)
                  throw new NullPointerException();
              
              this.baseType = baseType;
              this.typeFieldName = typeFieldName;
          }
  
  
          public <R> TypeAdapter<R> create(final Gson gson, final TypeToken<R> type)
          {
              if (baseType != Object.class && !type.getRawType().isInstance(baseType))
                  return null;
              
              return new TypeAdapter<R>()
              {
                  @Override
                  public R read(JsonReader in) throws IOException
                  {
                      JsonElement jsonElement = Streams.parse(in);                
                      TypeAdapter<R> delegate = gson.getDelegateAdapter(RuntimeTypeAdapterFactory.this, type);
                      
                      if (jsonElement.isJsonObject())
                      {
                          JsonElement typeField = jsonElement.getAsJsonObject().remove(typeFieldName);
                                              
                          if (typeField != null)
                          {
                              String type = typeField.getAsString();
                              
                              try
                              {
                                  Class<R> runtimeClass = (Class<R>)Class.forName(type);
                                  delegate = gson.getDelegateAdapter(RuntimeTypeAdapterFactory.this, TypeToken.get(runtimeClass));                        
                              }
                              catch (ClassNotFoundException e)
                              {
                                  throw new RuntimeException("Runtime class specified in JSON is invalid: " + type, e);
                              }
                          }
                      }
                      
                      return delegate.fromJsonTree(jsonElement);
                  }
  
  
                  @Override
                  public void write(JsonWriter out, R value) throws IOException
                  {
                      Class<R> runtimeClass = (Class<R>)value.getClass();
                      String typeName = runtimeClass.getName();
                      TypeAdapter<R> delegate = gson.getDelegateAdapter(RuntimeTypeAdapterFactory.this, TypeToken.get(runtimeClass));
                      JsonElement jsonElt = delegate.toJsonTree(value);
                      
                      if (jsonElt.isJsonObject())
                      {
                          JsonObject jsonObject = jsonElt.getAsJsonObject();
                          JsonObject clone = new JsonObject();
                          
                          // insert class name as first attribute
                          clone.add(typeFieldName, new JsonPrimitive(typeName));
                          for (Map.Entry<String, JsonElement> e : jsonObject.entrySet())
                              clone.add(e.getKey(), e.getValue());
                          
                          jsonElt = clone;
                      }
                      
                      Streams.write(jsonElt, out);
                  }
              }.nullSafe();
          }
      }
          
      
      public ModuleConfigJsonFile(String moduleConfigPath)
      {
          configFile = new File(moduleConfigPath);
          configMap = new LinkedHashMap<String, ModuleConfig>();
          
          // init json serializer/deserializer
          final GsonBuilder builder = new GsonBuilder();
          builder.setPrettyPrinting();
          builder.disableHtmlEscaping();
          builder.registerTypeAdapterFactory(new RuntimeTypeAdapterFactory<Object>(Object.class, OBJ_CLASS_FIELD));
          
          gson = builder.create();
          readJSON();
      }
      
      
      @Override
      public List<ModuleConfig> getAllModulesConfigurations()
      {
          if (configMap.isEmpty())
              readJSON();        
          return new ArrayList<ModuleConfig>(configMap.values());
      }
      
      
      @Override
      public boolean contains(String moduleID)
      {
          return configMap.containsKey(moduleID);
      }
      
  
      @Override
      public synchronized ModuleConfig get(String moduleID)
      {
          ModuleConfig conf = configMap.get(moduleID);
          if (conf == null)
              throw new RuntimeException("No configuration found for module id " + moduleID);
          return conf;
      }
  
  
      @Override
      public synchronized void add(ModuleConfig... configList)
      {
          // need to read again to get last saved version
          readJSON();
          
          for (ModuleConfig config: configList)
          {        
              ModuleConfig conf = configMap.get(config.id);
              if (conf != null)
                  throw new RuntimeException("Module " + config.name + " already exists");            
              
              configMap.put(config.id, config);
          }
          
          writeJSON();
      }
      
      
      @Override
      public synchronized void update(ModuleConfig... configList)
      {
          // need to read again to get last saved version
          readJSON();
          
          for (ModuleConfig config: configList)
          {
              // generate a new ID if non was provided
              if (config.id == null)
                  config.id = UUID.randomUUID().toString();
              
              configMap.put(config.id, config); 
          }
          
          writeJSON();
      }
      
      
      @Override
      public synchronized void remove(String... moduleIDs)
      {
          // need to read again to get last saved version
          readJSON();
          
          for (String moduleID: moduleIDs)
          {
              get(moduleID); // check if module exists
              configMap.remove(moduleID);
          }
              
          writeJSON();
      }
      
      
      @Override
      public synchronized void close()
      {
          // nothing to do
      }
      
      
      /*
       * Reads all modules configuration from the given JSON config file
       */
      private void readJSON()
      {
          FileReader reader = null;
          if (!configFile.exists())
              return;
          
          try
          {
              reader = new FileReader(configFile);            
              Type collectionType = new TypeToken<List<ModuleConfig>>(){}.getType();
              JsonReader jsonReader = new JsonReader(reader);
              List<ModuleConfig> configList = gson.fromJson(jsonReader, collectionType);
              
              // build module map
              configMap.clear();
              for (ModuleConfig config: configList)
              {
                  if (configMap.containsKey(config.id))
                      throw new RuntimeException("Duplicate module ID " + config.id);
                  configMap.put(config.id, config);
              }
          }
          catch (Exception e)
          {
              throw new RuntimeException("Error while parsing module config file " + configFile.getAbsolutePath(), e);
          }
          finally
          {
              try
              {
                  if (reader != null)
                      reader.close();
              }
              catch (IOException e)
              {
              }
          }
      }
      
      
      private void writeJSON()
      {
          FileWriter writer = null;
          
          try
          {
  