View Javadoc
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.djutils.base.Identifiable;
8   import org.djutils.exceptions.Throw;
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-2024 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&lt;Column&lt;?&gt;&gt;; 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&lt;?&gt;; column.
87       * @throws IllegalArgumentException if the column number is &lt; 0 or &gt; {@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&lt;?&gt;; 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&lt;?&gt;[].
166      * @return Class&lt;?&gt;[] 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 }