<commit_before>/*
 * Copyright 2000-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 org.intellij.plugins.hcl.terraform.config.model

import com.beust.klaxon.*
import com.intellij.openapi.application.Application
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import java.io.InputStream
import java.util.*

class TypeModelLoader(val external: Map<String, TypeModelProvider.Additional>) {

  val resources: MutableList<ResourceType> = arrayListOf()
  val dataSources: MutableList<DataSourceType> = arrayListOf()
  val providers: MutableList<ProviderType> = arrayListOf()
  val provisioners: MutableList<ProvisionerType> = arrayListOf()
  val functions: MutableList<Function> = arrayListOf()

  fun load(): TypeModel? {
    val application = ApplicationManager.getApplication()
    try {
      val providers = loadList("/terraform/model/providers.list")?.map { "providers/$it.json" } ?: emptyList()
      val provisioners = loadList("/terraform/model/provisioners.list")?.map { "provisioners/$it.json" } ?: emptyList()
      val resources: Collection<String> = (providers + provisioners + "functions.json").map { "/terraform/model/" + it }

      for (it in resources) {
        val file = it.ensureHavePrefix("/")
        val stream = getResource(file)
        if (stream == null) {
          LOG.warn("Resource '$file' was not found")
          continue
        }

        val json: JsonObject?
        try {
          json = stream.use {
            val parser = Parser()
            parser.parse(stream) as JsonObject?
          }
          if (json == null) {
            logErrorAndFailInInternalMode(application, "In file '$file' no JSON found")
            continue
          }
        } catch(e: Exception) {
          logErrorAndFailInInternalMode(application, "Failed to load json data from file '$file'", e)
          continue
        }
        try {
          parseFile(json, file)
        } catch(e: Throwable) {
          logErrorAndFailInInternalMode(application, "Failed to parse file '$file'", e)
        }
      }

      // TODO: Fetch latest model from github (?)
      return TypeModel(this.resources, this.dataSources, this.providers, this.provisioners, this.functions)
    } catch(e: Exception) {
      logErrorAndFailInInternalMode(application, "Failed to load Terraform Model", e)
      return null
    }
  }

  private fun logErrorAndFailInInternalMode(application: Application, msg: String, e: Throwable? = null) {
    val msg2 = if (e == null) msg else "$msg: ${e.message}"
    if (e == null) LOG.error(msg2) else LOG.error(msg2, e)
    if (application.isInternal) {
      throw AssertionError(msg2, e)
    }
  }

  private fun isPluginUnpacked(): Boolean {
    val resource = TypeModelProvider::class.java.getResource("/terraform/model/") ?: return false
    return resource.protocol == "file"
  }

  companion object {
    internal val LOG = Logger.getInstance(TypeModelLoader::class.java)

    fun getResource(path: String): InputStream? {
      return TypeModelProvider::class.java.getResourceAsStream(path)
    }

    fun getModelExternalInformation(path: String): Any? {
      return getResourceJson("/terraform/model-external/$path")
    }

    @Throws(RuntimeException::class, NullPointerException::class)
    fun getResourceJson(path: String): Any? {
      val stream = getResource(path) ?: return null
      stream.use {
        val parser = Parser()
        return parser.parse(stream)
      }<commit_msg>Load external plugins schemas from `~/.terraform.d/schemas/*.json` files on Linux/OSX and `%APPDATA%/terraform.d/schemas/*.json` on Windows.<commit_after>/*
 * Copyright 2000-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 org.intellij.plugins.hcl.terraform.config.model

import com.beust.klaxon.*
import com.intellij.openapi.application.Application
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.SystemInfo
import com.intellij.util.SystemProperties
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.util.*

class TypeModelLoader(val external: Map<String, TypeModelProvider.Additional>) {

  val resources: MutableList<ResourceType> = arrayListOf()
  val dataSources: MutableList<DataSourceType> = arrayListOf()
  val providers: MutableList<ProviderType> = arrayListOf()
  val provisioners: MutableList<ProvisionerType> = arrayListOf()
  val functions: MutableList<Function> = arrayListOf()

  fun load(): TypeModel? {
    val application = ApplicationManager.getApplication()
    try {
      val providers = loadList("/terraform/model/providers.list")?.map { "providers/$it.json" } ?: emptyList()
      val provisioners = loadList("/terraform/model/provisioners.list")?.map { "provisioners/$it.json" } ?: emptyList()
      val resources: Collection<String> = (providers + provisioners + "functions.json").map { "/terraform/model/" + it }

      for (it in resources) {
        val file = it.ensureHavePrefix("/")
        val stream = getResource(file)
        if (stream == null) {
          LOG.warn("Resource '$file' was not found")
          continue
        }

        loadOne(application, file, stream)
      }

      val schemas = getSharedSchemas()
      for (file in schemas) {
        val stream: FileInputStream
        try {
          stream = file.inputStream()
        } catch(e: Exception) {
          logErrorAndFailInInternalMode(application, "Cannot open stream for file '${file.absolutePath}'", e)
          continue
        }
        loadOne(application, file.absolutePath, stream)
      }

      // TODO: Fetch latest model from github (?)
      return TypeModel(this.resources, this.dataSources, this.providers, this.provisioners, this.functions)
    } catch(e: Exception) {
      logErrorAndFailInInternalMode(application, "Failed to load Terraform Model", e)
      return null
    }
  }

  private fun loadOne(application: Application, file: String, stream: InputStream) {
    val json: JsonObject?
    try {
      json = stream.use {
        val parser = Parser()
        parser.parse(stream) as JsonObject?
      }
      if (json == null) {
        logErrorAndFailInInternalMode(application, "In file '$file' no JSON found")
        return
      }
    } catch(e: Exception) {
      logErrorAndFailInInternalMode(application, "Failed to load json data from file '$file'", e)
      return
    }
    try {
      parseFile(json, file)
    } catch(e: Throwable) {
      logErrorAndFailInInternalMode(application, "Failed to parse file '$file'", e)
    }
    return
  }

  private fun logErrorAndFailInInternalMode(application: Application, msg: String, e: Throwable? = null) {
    val msg2 = if (e == null) msg else "$msg: ${e.message}"
    if (e == null) LOG.error(msg2) else LOG.error(msg2, e)
    if (application.isInternal) {
      throw AssertionError(msg2, e)
    }
  }

  private fun isPluginUnpacked(): Boolean {
    val resource = TypeModelProvider::class.java.getResource("/terraform/model/") ?: return false
    return resource.protocol == "file"
  }

  private fun getSharedSchemas(): List<File> {
    val terraform_d: File
    if (SystemInfo.isWindows) {
      val appdata = System.getenv("APPDATA") ?: return emptyList()
      terraform_d = File(appdata, "terraform.d")
    } else {
      val userHome = SystemProperties.getUserHome() ?: return emptyList()
      terraform_d = File(userHome, ".terraform.d")
    }
    if (!terraform_d.exists() || !terraform_d.isDirectory) return emptyList()
    val schemas = File(terraform_d, "schemas")
    if (!schemas.exists() || !schemas.isDirectory) return emptyList()
    val files = schemas.listFiles { dir, name ->
      name.endsWith(".json", true)
    }
    return files?.toList()?.filter { it.exists() && it.isFile } ?: emptyList()
  }

  companion object {
    internal val LOG = Logger.getInstance(TypeModelLoader::class.java)

    fun getResource(path: String): InputStream? {
      return TypeModelProvider::class.java.getResourceAsStream(path)
    }

    fun getModelExternalInformation(path: String): Any? {
      return getResourceJson("/terraform/model-external/$path")
    }

    @Throws(RuntimeException::class, NullPointerException::class)
    fun getResourceJson(path: String): Any? {
      val stream = getResource(path) ?: return null
      stream.use {
        val parser = Parser()
        return parser.parse(stream)
      }
{'fraction_non_alphanumeric': 0.11226523974399329, 'fraction_numerical': 0.0033574651138390515, 'mean_word_length': 3.303386004514673, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '4267020', 'n_tokens_mistral': 2749, 'n_tokens_neox': 2725, 'n_words': 740}