• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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