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 import static com.google.common.collect.ImmutableList.toImmutableList; 20 21 import com.google.auto.value.AutoValue; 22 import com.google.common.collect.ImmutableCollection; 23 import com.google.common.collect.ImmutableMap; 24 import com.google.common.collect.ImmutableSet; 25 import com.google.common.collect.Ordering; 26 import java.util.Comparator; 27 28 /** 29 * Representation of ordered {@link Column}s in a table. Schemas define columns in both 30 * {@code RangeTable} and {@code CsvTable}. 31 */ 32 @AutoValue 33 public abstract class Schema { 34 /** 35 * Builder for a table schema. Columns are ordered in the order in which they, or their owning 36 * group is added to the schema. 37 */ 38 public static final class Builder { 39 private final ImmutableSet.Builder<String> names = ImmutableSet.builder(); 40 private final ImmutableMap.Builder<String, Column<?>> columns = ImmutableMap.builder(); 41 private final ImmutableMap.Builder<String, ColumnGroup<?, ?>> groups = ImmutableMap.builder(); 42 43 /** Adds the given column to the schema. */ add(Column<?> column)44 public Builder add(Column<?> column) { 45 names.add(column.getName()); 46 columns.put(column.getName(), column); 47 return this; 48 } 49 50 /** Adds the given column group to the schema. */ add(ColumnGroup<?, ?> group)51 public Builder add(ColumnGroup<?, ?> group) { 52 names.add(group.prototype().getName()); 53 groups.put(group.prototype().getName(), group); 54 return this; 55 } 56 build()57 public Schema build() { 58 return new AutoValue_Schema(names.build(), columns.build(), groups.build()); 59 } 60 } 61 62 private static final Schema EMPTY = builder().build(); 63 64 /** Returns an empty schema with no assigned columns. */ empty()65 public static Schema empty() { 66 return EMPTY; 67 } 68 69 /** Returns a new schema builder. */ builder()70 public static Builder builder() { 71 return new Builder(); 72 } 73 74 // Visible for AutoValue only. Schema()75 Schema() {} 76 77 // List of column/group names used to determine column order: 78 // E.g. if "names" is: ["col1", "grp1", "col2", "col3"] 79 // You can have the table <<"col1", "grp1:xx", "grp1:yy", "col3">> 80 // Not all columns need to be present and groups are ordered contiguously as the group prefix 81 // appears in the names list. names()82 abstract ImmutableSet<String> names(); columns()83 abstract ImmutableMap<String, Column<?>> columns(); groups()84 abstract ImmutableMap<String, ColumnGroup<?, ?>> groups(); 85 86 /** 87 * Returns the column for the specified key string. For "plain" columns (not in groups) the key 88 * is just the column name. For group columns, the key takes the form "prefix:suffix", where the 89 * prefix is the name of the "prototype" column, and the "suffix" is an ID of a value within the 90 * group. For example: 91 * 92 * <pre> {@code 93 * // Schema has a plain column called "Type" in it. 94 * typeCol = table.getColumn("Type"); 95 * 96 * // Schema has a group called "Region" in it which can parse RegionCodes. 97 * usRegionCol = table.getColumn("Region:US"); 98 * }</pre> 99 */ getColumn(String key)100 public Column<?> getColumn(String key) { 101 int split = key.indexOf(':'); 102 Column<?> column; 103 if (split == -1) { 104 column = columns().get(key); 105 } else { 106 ColumnGroup<?, ?> group = groups().get(key.substring(0, split)); 107 checkArgument(group != null, "invalid column %s, not in schema: %s", key, this); 108 column = group.getColumnFromId(key.substring(split + 1)); 109 } 110 checkArgument(column != null, "invalid column %s, not in schema: %s", key, this); 111 return column; 112 } 113 114 /** Returns whether the given column is valid within this schema. */ isValidColumn(Column<T> column)115 public <T extends Comparable<T>> boolean isValidColumn(Column<T> column) { 116 int split = column.getName().indexOf(':'); 117 if (split == -1) { 118 return columns().containsValue(column); 119 } else { 120 ColumnGroup<?, ?> group = groups().get(column.getName().substring(0, split)); 121 return group != null && column.isIn(group); 122 } 123 } 124 125 /** 126 * Checks whether the given column is valid within this schema, otherwise throws 127 * IllegalArgumentException. This is expected to be internal use only, since table users are 128 * meant to always know which columns are valid. 129 */ checkColumn(Column<T> column)130 <T extends Comparable<T>> Column<T> checkColumn(Column<T> column) { 131 checkArgument(isValidColumn(column), "invalid column %s, not in schema: %s", column, this); 132 return column; 133 } 134 135 /** 136 * Returns whether the this schema has a subset of columns/groups, in the same order as the 137 * given schema. 138 */ isSubSchemaOf(Schema schema)139 public boolean isSubSchemaOf(Schema schema) { 140 return schema.columns().values().containsAll(columns().values()) 141 && schema.groups().entrySet().containsAll(groups().entrySet()) 142 && names().asList().equals( 143 schema.names().stream().filter(names()::contains).collect(toImmutableList())); 144 } 145 146 /** Returns an ordering for all columns in this schema. */ ordering()147 public Comparator<Column<?>> ordering() { 148 return Comparator 149 .comparing(Schema::getPrefix, Ordering.explicit(names().asList())) 150 .thenComparing(Schema::getSuffix); 151 } 152 getNames()153 public ImmutableSet<String> getNames() { 154 return names(); 155 } 156 getColumns()157 public ImmutableCollection<Column<?>> getColumns() { 158 return columns().values(); 159 } 160 getPrefix(Column<?> column)161 private static String getPrefix(Column<?> column) { 162 int split = column.getName().indexOf(':'); 163 return split != -1 ? column.getName().substring(0, split) : column.getName(); 164 } 165 getSuffix(Column<?> column)166 private static String getSuffix(Column<?> column) { 167 int split = column.getName().indexOf(':'); 168 return split == -1 ? "" : column.getName().substring(split + 1); 169 } 170 } 171