1 package org.djutils.data;
2
3 import java.util.Collection;
4 import java.util.LinkedHashSet;
5 import java.util.Set;
6
7 import org.djunits.Throw;
8 import org.djutils.base.Identifiable;
9 import org.djutils.immutablecollections.ImmutableArrayList;
10 import org.djutils.immutablecollections.ImmutableList;
11
12 /**
13 * Abstract table implementation taking care of the columns. Sub classes must provide an {@code Iterator} over {@code Record}s
14 * and may have methods to add data.
15 * <p>
16 * Copyright (c) 2020-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
17 * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
18 * </p>
19 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
20 * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
21 * @author <a href="https://dittlab.tudelft.nl">Wouter Schakel</a>
22 */
23 public abstract class Table implements Iterable<Row>, Identifiable
24 {
25
26 /** Id. */
27 private final String id;
28
29 /** Description. */
30 private final String description;
31
32 /** Columns. */
33 private final ImmutableList<Column<?>> columns;
34
35 /**
36 * Constructor.
37 * @param id String; id
38 * @param description String; description
39 * @param columns Collection<Column<?>>; columns
40 * @throws NullPointerException when id, description or columns is null
41 * @throws IllegalArgumentException when id is empty, duplicate column ids, or there are zero columns
42 */
43 public Table(final String id, final String description, final Collection<Column<?>> columns)
44 {
45 Throw.whenNull(id, "Id may not be null.");
46 Throw.whenNull(description, "Description may not be null.");
47 Throw.whenNull(columns, "Columns may not be null.");
48 Throw.when(id.length() == 0, IllegalArgumentException.class, "id cannot be empty");
49 Throw.when(columns.size() == 0, IllegalArgumentException.class, "there should be at least one column");
50 Set<String> ids = new LinkedHashSet<>();
51 columns.forEach((column) -> ids.add(column.getId()));
52 Throw.when(ids.size() != columns.size(), IllegalArgumentException.class, "Duplicate column ids are not allowed.");
53 this.id = id;
54 this.description = description;
55 this.columns = new ImmutableArrayList<>(columns);
56 }
57
58 /** {@inheritDoc} */
59 @Override
60 public String getId()
61 {
62 return this.id;
63 }
64
65 /**
66 * Returns the description.
67 * @return description
68 */
69 public String getDescription()
70 {
71 return this.description;
72 }
73
74 /**
75 * Returns the list of columns.
76 * @return list of columns
77 */
78 public ImmutableList<Column<?>> getColumns()
79 {
80 return this.columns;
81 }
82
83 /**
84 * Return a specific column.
85 * @param columnNumber int; number of the column.
86 * @return Column<?>; column.
87 * @throws IllegalArgumentException if the column number is < 0 or > {@code getNumberOfColumns() - 1}.
88 */
89 public Column<?> getColumn(final int columnNumber)
90 {
91 return this.columns.get(columnNumber);
92 }
93
94 /**
95 * Returns the number of columns.
96 * @return number of columns
97 */
98 public int getNumberOfColumns()
99 {
100 return this.columns.size();
101 }
102
103 /**
104 * Returns the number of the column in this table.
105 * @param column Column<?>; column.
106 * @return int; column number.
107 * @throws IllegalArgumentException if the column is not in the table.
108 */
109 public int getColumnNumber(final Column<?> column)
110 {
111 Throw.when(!this.columns.contains(column), IllegalArgumentException.class, "Column %s is not in the table.",
112 column.getId());
113 return this.columns.indexOf(column);
114 }
115
116 /**
117 * Returns the number of the column with given id.
118 * @param columnId String; column id.
119 * @return int; column number.
120 * @throws IllegalArgumentException if the column is not in the table.
121 */
122 public int getColumnNumber(final String columnId)
123 {
124 for (int columnNumber = 0; columnNumber < getNumberOfColumns(); columnNumber++)
125 {
126 if (this.columns.get(columnNumber).getId().equals(columnId))
127 {
128 return columnNumber;
129 }
130 }
131 throw new IllegalArgumentException("Column " + columnId + " is not in the table.");
132 }
133
134 /**
135 * Return the column ids as a String[].
136 * @return String[]; the column ids
137 */
138 public String[] getColumnIds()
139 {
140 String[] headers = new String[getNumberOfColumns()];
141 int index = 0;
142 for (Column<?> column : this.columns)
143 {
144 headers[index++] = column.getId();
145 }
146 return headers;
147 }
148
149 /**
150 * Return the column descriptions as a String[].
151 * @return String[] the column headers
152 */
153 public String[] getColumnDescriptions()
154 {
155 String[] descriptions = new String[getNumberOfColumns()];
156 int index = 0;
157 for (Column<?> column : this.columns)
158 {
159 descriptions[index++] = column.getDescription();
160 }
161 return descriptions;
162 }
163
164 /**
165 * Return the column data types as a Class<?>[].
166 * @return Class<?>[] the column data types
167 */
168 public Class<?>[] getColumnDataTypes()
169 {
170 Class<?>[] dataTypes = new Class[getNumberOfColumns()];
171 int index = 0;
172 for (Column<?> column : this.columns)
173 {
174 dataTypes[index++] = column.getValueType();
175 }
176 return dataTypes;
177 }
178
179 /**
180 * Return the column data types as a String[]. Each data type is presented as the full class name or the primitive name. In
181 * case of an array, the result is preceded by an "[" for each dimension. After one or more "[" symbols, the class name is
182 * preceded by an "L" for a non-primitive class or interface, and by "I" for integer, "Z" for boolean, "B" for byte, "C" for
183 * char, "D" for double, "F" for float, "J" for long and "S" for short. So for a column with a double, "double" is returned.
184 * For a column with a "Double", "java.lang.Double" is returned, for an int[][], "[[I" is returned, and for a Long[],
185 * "[Ljava.lang.Long" is returned.
186 * @return String[] the column data types as an array of Strings
187 */
188 public String[] getColumnDataTypeStrings()
189 {
190 String[] dataTypes = new String[getNumberOfColumns()];
191 int index = 0;
192 for (Column<?> column : this.columns)
193 {
194 dataTypes[index++] = column.getValueType().getName();
195 }
196 return dataTypes;
197 }
198
199 /**
200 * Returns whether the table is empty.
201 * @return whether the table is empty
202 */
203 public abstract boolean isEmpty();
204
205 /** {@inheritDoc} */
206 @Override
207 public String toString()
208 {
209 return "Table [id=" + this.id + ", description=" + this.description + ", columns=" + this.columns + "]";
210 }
211
212 }