@Override
 public void run() {
   System.out.println("Connection to the GADM database...");
   Connection conn;
   try {
     conn = GADM.connect_gadm();
   } catch (SQLException e) {
     e.printStackTrace(System.err);
     return;
   }
   System.out.println("Connected, start processing");
   String[] c;
   do {
     synchronized (to_process) {
       if (to_process.isEmpty()) {
         if (stop) break;
         try {
           to_process.wait();
         } catch (InterruptedException e) {
           break;
         }
         continue;
       }
       c = to_process.removeFirst();
     }
     try {
       process(conn, c[0], c[1]);
     } catch (SQLException e) {
       System.err.println("Error processing country " + c[0]);
       e.printStackTrace(System.err);
     }
   } while (true);
   System.out.println("Close GADM connection");
   try {
     conn.close();
   } catch (SQLException e) {
     e.printStackTrace(System.err);
   }
   synchronized (this) {
     stopped = true;
     this.notify();
   }
 }
  private static void process(Connection conn, String country_code, String gadmid)
      throws SQLException {
    System.out.println("Start processing country " + country_code);
    long start = System.currentTimeMillis();
    String country = "'" + country_code + "'";
    String[] fields1 = new String[] {"id", "country", "name", "type", "type_en"};
    String[] fields2 = new String[] {"id", "parent_id", "country", "name", "type", "type_en"};

    Statement s = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    StringBuilder q = new StringBuilder();
    q.append("SELECT ");
    for (int level = 1; level <= 5; level++) {
      if (level > 1) q.append(',');
      q.append("`ID_")
          .append(level)
          .append("`,`NAME_")
          .append(level)
          .append("`,`ENGTYPE_")
          .append(level)
          .append("`,`TYPE_")
          .append(level);
      if (level < 5) q.append("`,`VALIDTO_").append(level);
      q.append('`');
    }
    q.append(" FROM gadm2");
    // only this country
    q.append(" WHERE `ID_0`=").append(gadmid);
    // only valid entries
    q.append(" AND (");
    for (int level = 1; level <= 4; ++level) {
      if (level > 1) q.append(" OR ");
      q.append("`VALIDTO_").append(level).append("`='Present'");
    }
    q.append("OR `ID_5` IS NOT NULL)");
    s.execute(q.toString());
    ResultSet r = s.getResultSet();
    if (!r.next()) {
      // nothing
      r.close();
      s.close();
      return;
    }
    Map<Integer, Long> ids1 = new HashMap<Integer, Long>();
    Map<Integer, Map<Integer, Long>> ids2 = new HashMap<Integer, Map<Integer, Long>>();
    Map<Integer, Map<Integer, Map<Integer, Long>>> ids3 =
        new HashMap<Integer, Map<Integer, Map<Integer, Long>>>();
    Map<Integer, Map<Integer, Map<Integer, Map<Integer, Long>>>> ids4 =
        new HashMap<Integer, Map<Integer, Map<Integer, Map<Integer, Long>>>>();
    String[] values;
    String type, type_en;
    Long[] id = new Long[5];
    Integer[] gid = new Integer[5];
    boolean[] valid = new boolean[5];
    int max_level = 0;
    do {
      gid[1] = r.getInt("ID_1");
      if (gid[1] == null || gid[1] == 0) continue;
      if (max_level == 0) max_level = 1;
      id[1] = ids1.get(gid[1]);
      if (id[1] == null) {
        // this is a new level
        List<String> names = GADM.getNames(r.getString("NAME_1"));
        if (names.isEmpty()) {
          System.err.println("NO NAME for country " + gadmid + " level 1 id " + gid[1]);
          names.add("");
          // continue;
        }
        type = r.getString("TYPE_1");
        type_en = r.getString("ENGTYPE_1");
        if (type != null && type.length() == 0) type = null;
        if (type_en != null && type_en.length() == 0) type_en = null;
        if (type == null && type_en != null) {
          type = type_en;
          type_en = null;
        }
        if (type != null && type.equals(type_en)) type_en = null;
        if (type == null) type = "";
        synchronized (Adm.class) {
          id[1] = adm_id[0]++;
        }
        values =
            new String[] {
              Long.toString(id[1]),
              country,
              SQLProcessor.escape(names.get(0)),
              SQLProcessor.escape(type),
              SQLProcessor.escape(type_en)
            };
        SQLProcessor.insert_delay("`geography`.`adm1`", fields1, values);
        ids1.put(gid[1], id[1]);
        ids2.put(gid[1], new HashMap<Integer, Long>());
        ids3.put(gid[1], new HashMap<Integer, Map<Integer, Long>>());
        ids4.put(gid[1], new HashMap<Integer, Map<Integer, Map<Integer, Long>>>());
      }
      Integer gid5 = r.getInt("ID_5");
      if (gid5 != null && gid5 == 0) gid5 = null;
      for (int i = 2; i <= 4; ++i) valid[i] = "Present".equals(r.getString("VALIDTO_" + i));
      for (int level = 2; level < 5; ++level) {
        gid[level] = r.getInt("ID_" + level);
        if (gid[level] != null && gid[level] == 0) gid[level] = null;
        if (gid[level] == null) break;
        switch (level) {
          case 2:
            id[level] = ids2.get(gid[1]).get(gid[2]);
            break;
          case 3:
            id[level] = ids3.get(gid[1]).get(gid[2]).get(gid[3]);
            break;
          case 4:
            id[level] = ids4.get(gid[1]).get(gid[2]).get(gid[3]).get(gid[4]);
            break;
        }

        if (id[level] == null) {
          if (gid5 == null) {
            boolean all = true;
            for (int i = level; i <= 4; ++i)
              if (valid[i]) {
                all = false;
                break;
              }
            if (all) break; // no valid entry anymore
          }
          if (max_level < level) max_level = level;
          List<String> names = GADM.getNames(r.getString("NAME_" + level));
          if (names.isEmpty()) {
            System.err.println(
                "NO NAME for country " + gadmid + " level " + level + " id " + gid[level]);
            continue;
          }
          type = r.getString("TYPE_" + level);
          type_en = r.getString("ENGTYPE_" + level);
          if (type != null && type.length() == 0) type = null;
          if (type_en != null && type_en.length() == 0) type_en = null;
          if (type == null && type_en != null) {
            type = type_en;
            type_en = null;
          }
          if (type != null && type.equals(type_en)) type_en = null;
          if (type == null) type = "";
          synchronized (Adm.class) {
            id[level] = adm_id[level - 1]++;
          }
          values =
              new String[] {
                Long.toString(id[level]),
                Long.toString(id[level - 1]),
                country,
                SQLProcessor.escape(names.get(0)),
                SQLProcessor.escape(type),
                SQLProcessor.escape(type_en)
              };
          SQLProcessor.insert_delay("`geography`.`adm" + level + "`", fields2, values);
          switch (level) {
            case 2:
              ids2.get(gid[1]).put(gid[2], id[2]);
              ids3.get(gid[1]).put(gid[2], new HashMap<Integer, Long>());
              ids4.get(gid[1]).put(gid[2], new HashMap<Integer, Map<Integer, Long>>());
              break;
            case 3:
              ids3.get(gid[1]).get(gid[2]).put(gid[3], id[3]);
              ids4.get(gid[1]).get(gid[2]).put(gid[3], new HashMap<Integer, Long>());
              break;
            case 4:
              ids4.get(gid[1]).get(gid[2]).get(gid[3]).put(gid[4], id[4]);
              break;
          }
        }
      }
      if (gid5 != null) {
        max_level = 5;
        List<String> names = GADM.getNames(r.getString("NAME_5"));
        if (names.isEmpty()) {
          System.err.println("NO NAME for country " + gadmid + " level 5 id " + gid5);
          continue;
        }
        type = r.getString("TYPE_5");
        type_en = r.getString("ENGTYPE_5");
        if (type != null && type.length() == 0) type = null;
        if (type_en != null && type_en.length() == 0) type_en = null;
        if (type == null && type_en != null) {
          type = type_en;
          type_en = null;
        }
        if (type != null && type.equals(type_en)) type_en = null;
        if (type == null) type = "";
        long id5;
        synchronized (Adm.class) {
          id5 = adm_id[4]++;
        }
        values =
            new String[] {
              Long.toString(id5),
              Long.toString(id[4]),
              country,
              SQLProcessor.escape(names.get(0)),
              SQLProcessor.escape(type),
              SQLProcessor.escape(type_en)
            };
        SQLProcessor.insert_delay("`geography`.`adm5`", fields2, values);
      }
    } while (r.next());
    r.close();
    s.close();

    // update number of divisions
    SQLProcessor.add_query(
        "`geography`.`country`",
        "UPDATE `geography`.`country` SET `divisions`=" + max_level + " WHERE `code`=" + country);

    System.out.println(
        "Country "
            + country_code
            + " processed in "
            + ((System.currentTimeMillis() - start) / 1000)
            + "s.");
  }