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