content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to you 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.apache.calcite.adapter.file;
  
  import org.apache.calcite.schema.SchemaPlus;
  import org.apache.calcite.schema.Table;
  import org.apache.calcite.schema.impl.AbstractSchema;
  import org.apache.calcite.util.Source;
  import org.apache.calcite.util.Sources;
  import org.apache.calcite.util.Util;
  
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.ImmutableMap;
  
  import org.checkerframework.checker.nullness.qual.Nullable;
  
  import java.io.File;
  import java.util.List;
  import java.util.Map;
  
  /**
   * Schema mapped onto a set of URLs / HTML tables. Each table in the schema
   * is an HTML table on a URL.
   */
  class FileSchema extends AbstractSchema {
    private final ImmutableList<Map<String, Object>> tables;
    private final @Nullable File baseDirectory;
  
    /**
     * Creates an HTML tables schema.
     *
     * @param parentSchema  Parent schema
     * @param name          Schema name
     * @param baseDirectory Base directory to look for relative files, or null
     * @param tables        List containing HTML table identifiers, or null
     */
    FileSchema(SchemaPlus parentSchema, String name, @Nullable File baseDirectory,
        @Nullable List<Map<String, Object>> tables) {
      this.tables =
          tables == null ? ImmutableList.of()
              : ImmutableList.copyOf(tables);
      this.baseDirectory = baseDirectory;
    }
  
    /**
     * Looks for a suffix on a string and returns
     * either the string with the suffix removed
     * or the original string.
     */
    private static String trim(String s, String suffix) {
      String trimmed = trimOrNull(s, suffix);
      return trimmed != null ? trimmed : s;
    }
  
    /**
     * Looks for a suffix on a string and returns
     * either the string with the suffix removed
     * or null.
     */
    private static @Nullable String trimOrNull(String s, String suffix) {
      return s.endsWith(suffix)
          ? s.substring(0, s.length() - suffix.length())
          : null;
    }
  
    @Override protected Map<String, Table> getTableMap() {
      final ImmutableMap.Builder<String, Table> builder = ImmutableMap.builder();
  
      for (Map<String, Object> tableDef : this.tables) {
        addTable(builder, tableDef);
      }
  
      // Look for files in the directory ending in ".csv", ".csv.gz", ".json",
      // ".json.gz".
      if (baseDirectory != null) {
        final Source baseSource = Sources.of(baseDirectory);
        File[] files = baseDirectory.listFiles((dir, name) -> {
          final String nameSansGz = trim(name, ".gz");
          return nameSansGz.endsWith(".csv")
              || nameSansGz.endsWith(".json");
        });
        if (files == null) {
          System.out.println("directory " + baseDirectory + " not found");
          files = new File[0];
        }
        // Build a map from table name to table; each file becomes a table.
        for (File file : files) {
          Source source = Sources.of(file);
          Source sourceSansGz = source.trim(".gz");
          final Source sourceSansJson = sourceSansGz.trimOrNull(".json");
          if (sourceSansJson != null) {
            addTable(builder, source, sourceSansJson.relative(baseSource).path(),
                null);
          }
          final Source sourceSansCsv = sourceSansGz.trimOrNull(".csv");
          if (sourceSansCsv != null) {
            addTable(builder, source, sourceSansCsv.relative(baseSource).path(),
                null);
          }
        }
      }
  
      return builder.build();
    }
  
    private boolean addTable(ImmutableMap.Builder<String, Table> builder,
        Map<String, Object> tableDef) {
      final String tableName = (String) tableDef.get("name");
      final String url = (String) tableDef.get("url");
      final Source source0 = Sources.url(url);
      final Source source;
      if (baseDirectory == null) {
        source = source0;
      } else {
        source = Sources.of(baseDirectory).append(source0);
      }
      return addTable(builder, source, tableName, tableDef);
    }
  
    private static boolean addTable(ImmutableMap.Builder<String, Table> builder,
        Source source, String tableName, @Nullable Map<String, Object> tableDef) {
      final Source sourceSansGz = source.trim(".gz");
      final Source sourceSansJson = sourceSansGz.trimOrNull(".json");
      if (sourceSansJson != null) {
        final Table table = new JsonScannableTable(source);
        builder.put(Util.first(tableName, sourceSansJson.path()), table);
        return true;
      }
      final Source sourceSansCsv = sourceSansGz.trimOrNull(".csv");
      if (sourceSansCsv != null) {
        final Table table = new CsvTranslatableTable(source, null);
        builder.put(Util.first(tableName, sourceSansCsv.path()), table);
        return true;
      }
  
      if (tableDef != null) {
        try {
          FileTable table = FileTable.create(source, tableDef);
          builder.put(Util.first(tableName, source.path()), table);
          return true;
        } catch (Exception e) {
          throw new RuntimeException("Unable to instantiate table for: "
              + tableName);
        }
      }
  
      return false;
    }
  }
  