View Javadoc
1   /*
2   Copyright (c) 2011 James Ahlborn
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  
17  package com.healthmarketscience.jackcess;
18  
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Set;
24  
25  import com.healthmarketscience.jackcess.impl.DatabaseImpl;
26  import com.healthmarketscience.jackcess.impl.IndexData;
27  import com.healthmarketscience.jackcess.impl.IndexImpl;
28  import com.healthmarketscience.jackcess.impl.JetFormat;
29  import com.healthmarketscience.jackcess.impl.TableImpl;
30  import com.healthmarketscience.jackcess.impl.TableUpdater;
31  import com.healthmarketscience.jackcess.impl.ToStringBuilder;
32  
33  /**
34   * Builder style class for constructing an {@link Index}.  See {@link
35   * TableBuilder} for example usage.  Additionally, an Index can be added to an
36   * existing Table using the {@link #addToTable(Table)} method.
37   *
38   * @author James Ahlborn
39   * @see TableBuilder
40   * @usage _general_class_
41   */
42  public class IndexBuilder
43  {
44    /** name typically used by MS Access for the primary key index */
45    public static final String PRIMARY_KEY_NAME = "PrimaryKey";
46  
47    /** name of the new index */
48    private String _name;
49    /** the type of the index */
50    private byte _type;
51    /** additional index flags (UNKNOWN_INDEX_FLAG always seems to be set in
52        access 2000+) */
53    private byte _flags = IndexData.UNKNOWN_INDEX_FLAG;
54    /** the names and orderings of the indexed columns */
55    private final List<Column> _columns = new ArrayList<>();
56    /** 0-based index number */
57    private int _indexNumber;
58  
59    public IndexBuilder(String name) {
60      _name = name;
61    }
62  
63    public String getName() {
64      return _name;
65    }
66  
67    public byte getType() {
68      return _type;
69    }
70  
71    public byte getFlags() {
72      return _flags;
73    }
74  
75    public boolean isPrimaryKey() {
76      return (getType() == IndexImpl.PRIMARY_KEY_INDEX_TYPE);
77    }
78  
79    public boolean isUnique() {
80        return ((getFlags() & IndexData.UNIQUE_INDEX_FLAG) != 0);
81    }
82  
83    public boolean isIgnoreNulls() {
84        return ((getFlags() & IndexData.IGNORE_NULLS_INDEX_FLAG) != 0);
85    }
86  
87    public List<Column> getColumns() {
88      return _columns;
89    }
90  
91    /**
92     * Sets the name of the index.
93     */
94    public IndexBuilder setName(String name) {
95      _name = name;
96      return this;
97    }
98  
99    /**
100    * Adds the columns with ASCENDING ordering to the index.
101    */
102   public IndexBuilder addColumns(String... names) {
103     return addColumns(true, names);
104   }
105 
106   /**
107    * Adds the columns with the given ordering to the index.
108    */
109   public IndexBuilder addColumns(boolean ascending, String... names) {
110     if(names != null) {
111       for(String name : names) {
112         _columns.add(new Column(name, ascending));
113       }
114     }
115     return this;
116   }
117 
118   /**
119    * Sets this index to be a primary key index (additionally sets the index as
120    * unique and required).
121    */
122   public IndexBuilder setPrimaryKey() {
123     _type = IndexImpl.PRIMARY_KEY_INDEX_TYPE;
124     setRequired();
125     return setUnique();
126   }
127 
128   /**
129    * @usage _advanced_method_
130    */
131   public IndexBuilder setType(byte type) {
132     _type = type;
133     return this;
134   }
135 
136   /**
137    * Sets this index to enforce uniqueness.
138    */
139   public IndexBuilder setUnique() {
140     _flags |= IndexData.UNIQUE_INDEX_FLAG;
141     return this;
142   }
143 
144   /**
145    * Sets this index to enforce required.
146    */
147   public IndexBuilder setRequired() {
148     _flags |= IndexData.REQUIRED_INDEX_FLAG;
149     return this;
150   }
151 
152   /**
153    * Sets this index to ignore null values.
154    */
155   public IndexBuilder setIgnoreNulls() {
156     _flags |= IndexData.IGNORE_NULLS_INDEX_FLAG;
157     return this;
158   }
159 
160   /**
161    * @usage _advanced_method_
162    */
163   public int getIndexNumber() {
164     return _indexNumber;
165   }
166 
167   /**
168    * @usage _advanced_method_
169    */
170   public void setIndexNumber(int newIndexNumber) {
171     _indexNumber = newIndexNumber;
172   }
173 
174   /**
175    * Checks that this index definition is valid.
176    *
177    * @throws IllegalArgumentException if this index definition is invalid.
178    * @usage _advanced_method_
179    */
180   public void validate(Set<String> tableColNames, JetFormat format) {
181 
182     DatabaseImpl.validateIdentifierName(
183         getName(), format.MAX_INDEX_NAME_LENGTH, "index");
184 
185     if(getColumns().isEmpty()) {
186       throw new IllegalArgumentException(withErrorContext(
187           "index has no columns"));
188     }
189     if(getColumns().size() > IndexData.MAX_COLUMNS) {
190       throw new IllegalArgumentException(withErrorContext(
191           "index has too many columns, max " + IndexData.MAX_COLUMNS));
192     }
193 
194     Set<String> idxColNames = new HashSet<>();
195     for(Column col : getColumns()) {
196       String idxColName = DatabaseImpl.toLookupName(col.getName());
197       if(!idxColNames.add(idxColName)) {
198         throw new IllegalArgumentException(withErrorContext(
199             "duplicate column name " + col.getName() + " in index"));
200       }
201       if(!tableColNames.contains(idxColName)) {
202         throw new IllegalArgumentException(withErrorContext(
203             "column named " + col.getName() + " not found in table"));
204       }
205     }
206   }
207 
208   /**
209    * Adds a new Index to the given Table with the currently configured
210    * attributes.
211    */
212   public Index addToTable(Table table) throws IOException {
213     return addToTableDefinition(table);
214   }
215 
216   /**
217    * Adds a new Index to the given TableDefinition with the currently
218    * configured attributes.
219    */
220   public Index addToTableDefinition(TableDefinition table) throws IOException {
221       return new TableUpdater((TableImpl)table).addIndex(this);
222   }
223 
224   @Override
225   public String toString() {
226     return ToStringBuilder.builder(this)
227       .append("name", _name)
228       .append("number", _indexNumber)
229       .append("type", _type)
230       .append("isPrimaryKey", isPrimaryKey())
231       .append("isUnique", isUnique())
232       .append("ignoreNulls", isIgnoreNulls())
233       .append("columns", _columns)
234       .toString();
235   }
236 
237   private String withErrorContext(String msg) {
238     return msg + "(Index=" + getName() + ")";
239   }
240 
241   /**
242    * Information about a column in this index (name and ordering).
243    */
244   public static class Column
245   {
246     /** name of the column to be indexed */
247     private String _name;
248     /** column flags (ordering) */
249     private final byte _flags;
250 
251     private Column(String name, boolean ascending) {
252       _name = name;
253       _flags = (ascending ? IndexData.ASCENDING_COLUMN_FLAG : 0);
254     }
255 
256     public String getName() {
257       return _name;
258     }
259 
260     public Column setName(String name) {
261       _name = name;
262       return this;
263     }
264 
265     public boolean isAscending() {
266       return ((getFlags() & IndexData.ASCENDING_COLUMN_FLAG) != 0);
267     }
268 
269     public byte getFlags() {
270       return _flags;
271     }
272 
273     @Override
274     public String toString() {
275       return ToStringBuilder.valueBuilder(this)
276         .append("name", _name)
277         .append("flags", getFlags() + " " + (isAscending() ? "(ASC)" : "(DSC)"))
278         .toString();
279     }
280   }
281 
282 }