001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.sql.framework.model.impl;
042:
043: import java.util.ArrayList;
044: import java.util.Collections;
045: import java.util.Iterator;
046: import java.util.List;
047:
048: import org.netbeans.modules.sql.framework.model.DBColumn;
049: import org.w3c.dom.Element;
050:
051: import com.sun.sql.framework.exception.BaseException;
052: import com.sun.sql.framework.utils.StringUtil;
053: import java.sql.DatabaseMetaData;
054: import java.sql.ResultSet;
055: import java.sql.SQLException;
056: import java.util.Locale;
057: import java.util.ResourceBundle;
058: import org.netbeans.modules.sql.framework.model.DBTable;
059: import org.netbeans.modules.sql.framework.model.Index;
060:
061: /**
062: * Implements Index interface.
063: *
064: * @author Jonathan Giron
065: * @version $Revision$
066: */
067: public class IndexImpl implements Cloneable, Index {
068:
069: /**
070: * Intermediate container class to hold metadata of columns involved in a particular
071: * Index.
072: */
073: public static class Column implements Comparable {
074: private String name;
075: private int sequence;
076:
077: /**
078: * Creates a new instance of Index.Column with the given DBColumn and sequence.
079: *
080: * @param col new DBColumn
081: * @param colSequence sequence of new column w.r.t. other columns
082: */
083: public Column(DBColumn col, int colSequence) {
084: this (col.getName(), colSequence);
085: }
086:
087: /**
088: * Creates a new instance of Index.Column with the given name and sequence.
089: *
090: * @param colName name of new column
091: * @param colSequence sequence of new column w.r.t. other columns
092: */
093: public Column(String colName, int colSequence) {
094: if (colName == null || colName.trim().length() == 0) {
095: throw new IllegalArgumentException(
096: "Must supply non-empty String value for parameter colName.");
097: }
098:
099: if (colSequence <= 0) {
100: throw new IllegalArgumentException(
101: "Must supply positive integer value for parameter colSequence.");
102: }
103:
104: name = colName;
105: sequence = colSequence;
106: }
107:
108: /**
109: * Compares this object with the specified object for order. Returns a negative
110: * integer, zero, or a positive integer as this object is less than, equal to, or
111: * greater than the specified object.
112: * <p>
113: * Note: this class has a natural ordering that is inconsistent with equals.
114: *
115: * @param o the Object to be compared.
116: * @return a negative integer, zero, or a positive integer as this object is less
117: * than, equal to, or greater than the specified object.
118: */
119: public int compareTo(Object o) {
120: return (this .sequence - ((Column) o).sequence);
121: }
122:
123: /**
124: * Gets name of this column.
125: *
126: * @return column name
127: */
128: public String getName() {
129: return name;
130: }
131:
132: /**
133: * Gets sequence of this column with respect to others in the associated index.
134: *
135: * @return column index
136: */
137: public int getSequence() {
138: return sequence;
139: }
140: }
141:
142: /** Name of attribute used for marshalling out cardinality value to XML */
143: public static final String CARDINALITY_ATTR = "cardinality"; // NOI18N
144:
145: /** List of column names in key sequence order. */
146: public static final String COLUMNS_ATTR = "columns"; // NOI18N
147:
148: /** Document element tag name for marshalling out this object to XML */
149: public static final String ELEMENT_TAG = "index"; // NOI18N
150:
151: /** Name of attribute used for marshalling out index name to XML */
152: public static final String NAME_ATTR = "name"; // NOI18N
153:
154: /** Name of attribute used for marshalling out sort order value to XML */
155: public static final String SORTORDER_ATTR = "sortOrder"; // NOI18N
156:
157: /** Name of attribute used for marshalling out index type to XML */
158: public static final String TYPE_ATTR = "type"; // NOI18N
159:
160: /** Name of attribute used for marshalling out uniqueness flag to XML */
161: public static final String UNIQUE_ATTR = "unique"; // NOI18N
162:
163: /* Indicates number of unique values in index */
164: private int cardinality;
165:
166: /* List of column names in key sequence order. */
167: private List<String> columnNames;
168:
169: /* (optional) DOM element used to construct this instance of Index */
170: private transient Element element;
171:
172: /* Name of this index */
173: private String name;
174:
175: /* DBTable to which this Index belongs */
176: private DBTable parent;
177:
178: /* Indicates sort order, if any, of index */
179: private String sortSequence;
180:
181: /* Type of index, as enumerated in DatabaseMetaData */
182: private int type;
183:
184: /* Indicates whether index is unique */
185: private boolean unique = false;
186:
187: private static final String RS_INDEX_NAME = "INDEX_NAME"; // NOI18N
188:
189: private static final String RS_COLUMN_NAME = "COLUMN_NAME"; // NOI18N
190:
191: private static final String RS_NON_UNIQUE = "NON_UNIQUE"; // NOI18N
192:
193: private static final String RS_TYPE = "TYPE"; // NOI18N
194:
195: private static final String RS_ORDINAL = "ORDINAL_POSITION"; // NOI18N
196:
197: private static final String RS_ASC_OR_DESC = "ASC_OR_DESC"; // NOI18N
198:
199: private static final String RS_CARDINALITY = "CARDINALITY"; // NOI18N
200:
201: /**
202: * Creates a List of IndexColumn instances from the given ResultSet.
203: *
204: * @param rs ResultSet containing index metadata as obtained from
205: * DatabaseMetaData
206: * @return List of IndexColumn instances based from metadata in rs'
207: *
208: * @throws SQLException if SQL error occurs while reading in data from
209: * given ResultSet
210: */
211: public static List<IndexImpl> createIndexList(ResultSet rs)
212: throws SQLException {
213: List<IndexImpl> indices = Collections.emptyList();
214:
215: if (rs != null && rs.next()) {
216: indices = new ArrayList<IndexImpl>();
217: do {
218: IndexImpl newIndex = new IndexImpl(rs);
219:
220: // Ignore statistics indexes as they are relevant only to the
221: // DB which sourced this metadata.
222: if (newIndex.getType() != DatabaseMetaData.tableIndexStatistic) {
223: indices.add(newIndex);
224: }
225: } while (rs.next());
226: }
227:
228: return indices;
229: }
230:
231: IndexImpl(ResultSet rs) throws SQLException {
232: if (rs == null) {
233: Locale locale = Locale.getDefault();
234: ResourceBundle cMessages = ResourceBundle
235: .getBundle(
236: "org/netbeans/modules/sql/framework/model/impl/Bundle",
237: locale); // NO i18n
238: throw new IllegalArgumentException(cMessages
239: .getString("ERROR_VALID_RS")
240: + "(ERROR_VALID_RS)"); // NOI18N
241: }
242:
243: name = rs.getString(RS_INDEX_NAME);
244: //columnName = rs.getString(RS_COLUMN_NAME);
245:
246: unique = !(rs.getBoolean(RS_NON_UNIQUE));
247: type = rs.getShort(RS_TYPE);
248:
249: //ordinalPosition = rs.getShort(RS_ORDINAL);
250: sortSequence = rs.getString(RS_ASC_OR_DESC);
251: cardinality = rs.getInt(RS_CARDINALITY);
252: }
253:
254: /**
255: * Creates a new instance of IndexImpl, using the given keyElement as a source for
256: * reconstituting its contents. Caller must invoke parseXML() after this constructor
257: * returns in order to unmarshal and reconstitute the instance object.
258: *
259: * @param keyElement DOM element containing XML marshalled version of a IndexImpl
260: * instance
261: * @see #parseXML
262: */
263: public IndexImpl(Element keyElement) {
264: this ();
265: element = keyElement;
266: }
267:
268: /**
269: * Creates a new instance of Index, cloning the contents of the given Index
270: * implementation instance.
271: *
272: * @param src Index instance to be cloned
273: */
274: public IndexImpl(Index src) {
275: this ();
276: copyFrom(src);
277: }
278:
279: /**
280: * Creates a new instance of Index with the given key name and attributes.
281: *
282: * @param indexName name of this Index, must be non-empty
283: * @param indexType type of Index, as enumerated in java.sql.DatabaseMetaData; one of
284: * tableIndexClustered, tableIndexHashed, or tableIndexOther
285: * @param isUnique true if index enforces uniqueness, false otherwise
286: * @param sortOrder 'A' for ascending, 'D' for descending, null if undefined
287: * @param indexCardinality cardinality of this index
288: * @see java.sql.DatabaseMetaData#tableIndexClustered
289: * @see java.sql.DatabaseMetaData#tableIndexHashed
290: * @see java.sql.DatabaseMetaData#tableIndexOther
291: */
292: public IndexImpl(String indexName, int indexType, boolean isUnique,
293: String sortOrder, int indexCardinality) {
294: this ();
295:
296: if (indexName == null) {
297: throw new IllegalArgumentException(
298: "Must supply non-empty String ref for indexName param.");
299: }
300:
301: name = indexName;
302: type = indexType;
303: unique = isUnique;
304: sortSequence = sortOrder;
305: cardinality = indexCardinality;
306: }
307:
308: /**
309: * Creates a new instance of Index with the given key name and attributes, and
310: * referencing the column names in the given List.
311: *
312: * @param indexName name of this Index, must be non-empty
313: * @param indexType type of Index, as enumerated in java.sql.DatabaseMetaData; one of
314: * tableIndexClustered, tableIndexHashed, or tableIndexOther
315: * @param isUnique true if index enforces uniqueness, false otherwise
316: * @param sortOrder 'A' for ascending, 'D' for descending, null if undefined
317: * @param indexCardinality cardinality of this index
318: * @param indexColumnNames List of Column objects, or column names in sequential
319: * order, depending on state of isStringList
320: * @param isStringList true if indexColumnName contains column names in sequential
321: * order, false if it contains Column objects which need to be sorted in
322: * sequential order.
323: * @see java.sql.DatabaseMetaData#tableIndexClustered
324: * @see java.sql.DatabaseMetaData#tableIndexHashed
325: * @see java.sql.DatabaseMetaData#tableIndexOther
326: */
327: public IndexImpl(String indexName, int indexType, boolean isUnique,
328: String sortOrder, int indexCardinality,
329: List indexColumnNames, boolean isStringList) {
330: this (indexName, indexType, isUnique, sortOrder,
331: indexCardinality);
332: setColumnNames(indexColumnNames, isStringList);
333: }
334:
335: /*
336: * IMPLEMENTATION OF Index
337: */
338:
339: /* Private no-arg constructor */
340: private IndexImpl() {
341: name = null;
342: columnNames = new ArrayList<String>();
343: }
344:
345: /**
346: * Create a clone of this PrimaryKeyImpl.
347: *
348: * @return cloned copy of DBColumn.
349: */
350: @Override
351: public Object clone() {
352: try {
353: IndexImpl impl = (IndexImpl) super .clone();
354: impl.columnNames = new ArrayList<String>(this .columnNames);
355:
356: return impl;
357: } catch (CloneNotSupportedException e) {
358: throw new InternalError(e.toString());
359: }
360: }
361:
362: /**
363: * @see org.netbeans.modules.model.database.Index#contains(DBColumn)
364: */
365: public boolean contains(DBColumn col) {
366: return (col != null) ? contains(col.getName()) : false;
367: }
368:
369: /**
370: * @see org.netbeans.modules.model.database.Index#contains(java.lang.String)
371: */
372: public boolean contains(String columnName) {
373: return columnNames.contains(columnName);
374: }
375:
376: /**
377: * Overrides default implementation to return value based on memberwise comparison.
378: *
379: * @param refObj Object against which we compare this instance
380: * @return true if refObj is functionally identical to this instance; false otherwise
381: */
382: @Override
383: public boolean equals(Object refObj) {
384: if (this == refObj) {
385: return true;
386: }
387:
388: if (!(refObj instanceof IndexImpl)) {
389: return false;
390: }
391:
392: IndexImpl ref = (IndexImpl) refObj;
393:
394: boolean result = (name != null) ? name.equals(ref.name)
395: : (ref.name == null);
396:
397: result &= (type == ref.type)
398: && (cardinality == ref.cardinality)
399: && (unique == ref.unique);
400:
401: result &= (sortSequence != null) ? sortSequence
402: .equals(ref.sortSequence) : (ref.sortSequence == null);
403:
404: result &= (columnNames != null) ? columnNames
405: .equals(ref.columnNames) : (ref.columnNames == null);
406:
407: return result;
408: }
409:
410: /**
411: * @see org.netbeans.modules.model.database.Index#getCardinality
412: */
413: public int getCardinality() {
414: return cardinality;
415: }
416:
417: /**
418: * @see org.netbeans.modules.model.database.Index#getColumnCount
419: */
420: public int getColumnCount() {
421: return columnNames.size();
422: }
423:
424: /**
425: * @see org.netbeans.modules.model.database.Index#getColumnName
426: */
427: public String getColumnName(int iColumn) {
428: return columnNames.get(iColumn);
429: }
430:
431: /**
432: * @see org.netbeans.modules.model.database.Index#getColumnNames
433: */
434: public List<String> getColumnNames() {
435: return Collections.unmodifiableList(columnNames);
436: }
437:
438: /**
439: * @see org.netbeans.modules.model.database.Index#getName
440: */
441: public String getName() {
442: return name;
443: }
444:
445: /**
446: * @see org.netbeans.modules.model.database.Index#getParent
447: */
448: public DBTable getParent() {
449: return parent;
450: }
451:
452: /**
453: * @see org.netbeans.modules.model.database.Index#getSequence(DBColumn)
454: */
455: public int getSequence(DBColumn col) {
456: if (col == null || col.getName() == null) {
457: return -1;
458: }
459:
460: return getSequence(col.getName().trim());
461: }
462:
463: /*
464: * Setter and non-API helper methods
465: */
466:
467: /**
468: * Gets the ordinal position of the column, if any, associated with the given
469: * columnName.
470: *
471: * @param columnName name of column whose position is desired
472: * @return (zero-based) position of given column, or -1 if no column by the given
473: * columnName could be located
474: */
475: public int getSequence(String columnName) {
476: return columnNames.indexOf(columnName);
477: }
478:
479: /**
480: * @see org.netbeans.modules.model.database.Index#getSortSequence
481: */
482: public String getSortSequence() {
483: return sortSequence;
484: }
485:
486: /**
487: * @see org.netbeans.modules.model.database.Index#getType
488: */
489: public int getType() {
490: return type;
491: }
492:
493: /**
494: * Overrides default implementation to compute hashCode value for those members used
495: * in equals() for comparison.
496: *
497: * @return hash code for this object
498: * @see java.lang.Object#hashCode
499: */
500: @Override
501: public int hashCode() {
502: int myHash = (name != null) ? name.hashCode() : 0;
503: myHash += (columnNames != null) ? columnNames.hashCode() : 0;
504:
505: myHash += type + cardinality + (unique ? 1 : 0);
506: myHash += (sortSequence != null) ? sortSequence.hashCode() : 0;
507:
508: return myHash;
509: }
510:
511: /**
512: * @see org.netbeans.modules.model.database.Index#contains(java.lang.String)
513: */
514: public boolean isUnique() {
515: return unique;
516: }
517:
518: /**
519: * Parses the XML content, if any, represented by the DOM element member variable.
520: *
521: * @exception BaseException thrown while parsing XML, or if member variable element is
522: * null
523: */
524: public void parseXML() throws BaseException {
525: if (this .element == null) {
526: throw new BaseException("No <" + ELEMENT_TAG
527: + "> element found.");
528: }
529:
530: this .name = element.getAttribute(NAME_ATTR);
531:
532: String val = element.getAttribute(TYPE_ATTR);
533: try {
534: this .type = Integer.parseInt(val);
535: } catch (NumberFormatException e) {
536: this .type = 0;
537: } catch (NullPointerException e) {
538: this .type = 0;
539: }
540:
541: val = element.getAttribute(UNIQUE_ATTR);
542: try {
543: this .unique = Boolean.valueOf(val).booleanValue();
544: } catch (NullPointerException e) {
545: this .type = 0;
546: }
547:
548: this .sortSequence = element.getAttribute(SORTORDER_ATTR);
549:
550: val = element.getAttribute(CARDINALITY_ATTR);
551: try {
552: this .cardinality = Integer.parseInt(val);
553: } catch (NumberFormatException e) {
554: this .cardinality = 0;
555: } catch (NullPointerException e) {
556: this .cardinality = 0;
557: }
558:
559: String colNames = element.getAttribute(COLUMNS_ATTR);
560: columnNames.addAll(StringUtil.createStringListFrom(colNames));
561: }
562:
563: /**
564: * Sets column names associated with this index from the given List, using the given
565: * flag to interpret the type of objects contained in the list.
566: *
567: * @param indexColumnNames List of column names (either as Index.Column objects or
568: * String values)
569: * @param isStringList true if List contains column names as Strings, false if List
570: * contains Index.Column objects.
571: */
572: public void setColumnNames(List<String> indexColumnNames,
573: boolean isStringList) {
574: if (isStringList) {
575: columnNames.addAll(indexColumnNames);
576: } else {
577: Collections.sort(indexColumnNames);
578: Iterator iter = indexColumnNames.iterator();
579: while (iter.hasNext()) {
580: Column col = (Column) iter.next();
581: columnNames.add(col.getName());
582: }
583: }
584: }
585:
586: /**
587: * Replaces the current List of column names with the contents of the given String
588: * array.
589: *
590: * @param newColNames array of names to supplant current list of column names
591: */
592: public void setColumnNames(String[] newColNames) {
593: if (newColNames == null) {
594: throw new IllegalArgumentException(
595: "Must supply non-null String[] for param newColNames.");
596: }
597:
598: columnNames.clear();
599: for (int i = 0; i < newColNames.length; i++) {
600: columnNames.add(newColNames[i]);
601: }
602: }
603:
604: /**
605: * Sets reference to SQLTable that owns this primary key.
606: *
607: * @param newParent new parent of this primary key.
608: */
609: public void setParent(DBTable newParent) {
610: parent = newParent;
611: }
612:
613: /**
614: * Gets the default XML representation of index metadata.
615: *
616: * @return XML representation of the index metadata.
617: */
618: public synchronized String toXMLString() {
619: return toXMLString(null);
620: }
621:
622: /**
623: * Gets the XML representation of index metadata, using the given String as a prefix
624: * for successive elements.
625: *
626: * @param prefix start-of-line prefix for the XML representation.
627: * @return XML representation of the index metadata.
628: */
629: public synchronized String toXMLString(String prefix) {
630: if (prefix == null) {
631: prefix = "";
632: }
633:
634: StringBuilder buf = new StringBuilder(100);
635:
636: buf.append(prefix).append("<").append(ELEMENT_TAG).append(" ");
637: if (name != null && name.trim().length() != 0) {
638: buf.append(NAME_ATTR).append("=\"").append(name.trim())
639: .append("\" ");
640: }
641:
642: buf.append(TYPE_ATTR).append("=\"").append(type).append("\" ");
643:
644: buf.append(UNIQUE_ATTR).append("=\"").append(unique).append(
645: "\" ");
646:
647: if (sortSequence != null && sortSequence.trim().length() != 0) {
648: buf.append(SORTORDER_ATTR).append("=\"").append(
649: sortSequence).append("\" ");
650: }
651:
652: buf.append(CARDINALITY_ATTR).append("=\"").append(cardinality)
653: .append("\" ");
654:
655: if (columnNames.size() != 0) {
656: buf.append(COLUMNS_ATTR).append("=\"");
657: for (int i = 0; i < columnNames.size(); i++) {
658: if (i != 0) {
659: buf.append(",");
660: }
661: buf.append((columnNames.get(i)).trim());
662: }
663: buf.append("\" ");
664: }
665:
666: buf.append("/>\n");
667:
668: return buf.toString();
669: }
670:
671: private void copyFrom(Index src) {
672: name = src.getName();
673: parent = src.getParent();
674:
675: columnNames.clear();
676: columnNames.addAll(src.getColumnNames());
677:
678: type = src.getType();
679: unique = src.isUnique();
680: sortSequence = src.getSortSequence();
681: cardinality = src.getCardinality();
682: }
683: }
|