1 /* 2 * Copyright (C) 2017 The Libphonenumber Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.i18n.phonenumbers.metadata.table; 17 18 import static com.google.common.base.Preconditions.checkArgument; 19 20 import com.google.auto.value.AutoValue; 21 import com.google.common.collect.ImmutableList; 22 import java.io.IOException; 23 import java.io.Reader; 24 import java.nio.file.Files; 25 import java.nio.file.Path; 26 import java.util.ArrayList; 27 import java.util.Comparator; 28 import java.util.List; 29 import java.util.Optional; 30 import java.util.function.BiConsumer; 31 32 /** 33 * A CSV schema is a combination of a key marshaller and table columns. A CSV schema defines a 34 * CSV table with key columns, followed by non-key columns. 35 */ 36 @AutoValue 37 public abstract class CsvSchema<K> { 38 /** 39 * Returns a schema for a CSV file using the given marshaller to define key columns, and a table 40 * schema to define any additional columns in a row. 41 */ of(CsvKeyMarshaller<K> marshaller, Schema columns)42 public static <K> CsvSchema<K> of(CsvKeyMarshaller<K> marshaller, Schema columns) { 43 return new AutoValue_CsvSchema<>(marshaller, columns); 44 } 45 46 /** The marshaller defining table keys and how they are serialized in CSV. */ keyMarshaller()47 public abstract CsvKeyMarshaller<K> keyMarshaller(); 48 49 /** The table schema defining non-key columns in the table. */ columns()50 public abstract Schema columns(); 51 52 /** Returns the ordering for keys in the CSV table, as defined by the key marshaller. */ rowOrdering()53 public Optional<Comparator<K>> rowOrdering() { 54 return keyMarshaller().ordering(); 55 } 56 57 /** 58 * Returns the ordering for additional non-key columns in the CSV table as defined by the table 59 * schema. 60 */ columnOrdering()61 public Comparator<Column<?>> columnOrdering() { 62 return columns().ordering(); 63 } 64 65 /** 66 * Extracts the non-key columns of a table from the header row. The header row is expected to 67 * contain the names of all columns (including key columns) in the CSV table and this method 68 * verifies that the key columns are present as expected before resolving the non-key columns 69 * in order. 70 */ parseHeader(List<String> header)71 public ImmutableList<Column<?>> parseHeader(List<String> header) { 72 int hsize = keyMarshaller().getColumns().size(); 73 checkArgument(header.size() >= hsize, "CSV header too short: %s", header); 74 checkArgument(header.subList(0, hsize).equals(keyMarshaller().getColumns()), 75 "Invalid CSV header: %s", header); 76 ImmutableList.Builder<Column<?>> columns = ImmutableList.builder(); 77 header.subList(hsize, header.size()).forEach(s -> columns.add(columns().getColumn(s))); 78 return columns.build(); 79 } 80 81 /** Parses a row from a CSV table containing unescaped values. */ parseRow( ImmutableList<Column<?>> columns, List<String> row, BiConsumer<K, List<Assignment<?>>> fn)82 public void parseRow( 83 ImmutableList<Column<?>> columns, List<String> row, BiConsumer<K, List<Assignment<?>>> fn) { 84 int hsize = keyMarshaller().getColumns().size(); 85 checkArgument(row.size() >= hsize, "CSV row too short: %s", row); 86 K key = keyMarshaller().deserialize(row.subList(0, hsize)); 87 List<Assignment<?>> rowAssignments = new ArrayList<>(); 88 for (int n = 0; n < row.size() - hsize; n++) { 89 Column<?> c = columns.get(n); 90 rowAssignments.add( 91 Assignment.ofOptional(c, Optional.ofNullable(c.parse(row.get(n + hsize))))); 92 } 93 fn.accept(key, rowAssignments); 94 } 95 load(Path file)96 public CsvTable<K> load(Path file) throws IOException { 97 if (!Files.exists(file)) { 98 return CsvTable.builder(this).build(); 99 } 100 try (Reader csv = Files.newBufferedReader(file)) { 101 return CsvTable.importCsv(this, csv); 102 } 103 } 104 load(Reader reader)105 public CsvTable<K> load(Reader reader) throws IOException { 106 return CsvTable.importCsv(this, reader); 107 } 108 } 109