/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.jdbc.schema;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.identifier.DBIdentifier.DBIdentifierType;
import org.apache.openjpa.jdbc.identifier.QualifiedDBIdentifier;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.lib.meta.SourceTracker;

/**
 * Represents a database table.
 *
 * @author Abe White
 * @author Stephen Kim
 */

public class Table
    extends NameSet
    implements Comparable<Object>, SourceTracker {

    private static final long serialVersionUID = 1L;
    private DBIdentifier _name = DBIdentifier.NULL;
    private DBIdentifier _schemaName = DBIdentifier.NULL;
    // all keys must be normalized with normalizeColumnKey
    private Map<DBIdentifier, Column> _colMap = null;
    private Map<DBIdentifier, Index> _idxMap = null;
    private Collection<ForeignKey> _fkList = null;
    private Collection<Unique> _unqList = null;
    private Schema _schema = null;
    private PrimaryKey _pk = null;

    // keep track of source
    private File _source = null;
    private int _srcType = SRC_OTHER;

    // cache
    private Column[] _cols = null;
    private Column[] _autoAssign = null;
    private Column[] _rels = null;
    private ForeignKey[] _fks = null;
    private Index[] _idxs = null;
    private Unique[] _unqs = null;
    private String _comment = null;
    private int _lineNum = 0;
    private int _colNum = 0;
    private boolean _isAssociation = false;
    private QualifiedDBIdentifier _fullPath = null;

    /**
     * Default constructor.
     */
    public Table() {
    }

    /**
     * Constructor.
     *
     * @param name the table name
     * @param schema the table schema
     * @deprecated
     */
    @Deprecated
    public Table(String name, Schema schema) {
        this(DBIdentifier.newTable(name), schema);
    }

    public Table(DBIdentifier name, Schema schema) {
        setIdentifier(name);
        addName(name, true);
        if (schema != null)
            setSchemaIdentifier(schema.getIdentifier());
        _schema = schema;
    }

    public void setAssociation() {
        _isAssociation = true;
    }

    public boolean isAssociation() {
        return _isAssociation;
    }

    /**
     * Called when the table is removed from its schema. Removes all table
     * members, and invalidates the table.
     */
    void remove() {
        ForeignKey[] fks = getForeignKeys();
        for (ForeignKey fk : fks) {
            removeForeignKey(fk);
        }
        Index[] idxs = getIndexes();
        for (Index idx : idxs) {
            removeIndex(idx);
        }
        Unique[] unqs = getUniques();
        for (Unique unq : unqs) {
            removeUnique(unq);
        }
        removePrimaryKey();
        Column[] cols = getColumns();
        for (Column col : cols) {
            removeColumn(col);
        }
        _schema = null;
        _schemaName = DBIdentifier.NULL;
        _fullPath = null;
    }

    /**
     * Return the schema for the table.
     */
    public Schema getSchema() {
        return _schema;
    }

    /**
     * The table's schema name.
     * @deprecated
     */
    @Deprecated
    public String getSchemaName() {
        return getSchemaIdentifier().getName();
    }

    public DBIdentifier getSchemaIdentifier() {
        return _schemaName == null ? DBIdentifier.NULL : _schemaName;
    }

    /**
     * The table's schema name. You can only call this method on tables
     * whose schema object is not set.
     * @deprecated
     */
    @Deprecated
    public void setSchemaName(String name) {
        setSchemaIdentifier(DBIdentifier.newSchema(name));
    }

    public void setSchemaIdentifier(DBIdentifier name) {
        if (getSchema() != null)
            throw new IllegalStateException();
        _schemaName = name;
        _fullPath = null;
    }

    /**
     * Return the name of the table.
     * @deprecated
     */
    @Deprecated
    public String getName() {
        return getIdentifier().getName();
    }

    public DBIdentifier getIdentifier() {
        return _name == null ? DBIdentifier.NULL : _name;
    }

    /**
     * Set the name of the table. This method can only be called on tables
     * that are not part of a schema.
     * @deprecated use {@link #setIdentifier(DBIdentifier)} instead.
     */
    @Deprecated
    public void setName(String name) {
        setIdentifier(DBIdentifier.newTable(name));
    }

    /**
     * Set the name of the table. This method can only be called on tables which are not part of a schema.
     * @param name The name of the table.
     * @throws IllegalStateException if {@link #getSchema()} does not return null.
     */
    public void setIdentifier(DBIdentifier name) {
        if (getSchema() != null)
            throw new IllegalStateException();
        _name = name;
        _fullPath = null;
    }

    /**
     * Return the table name, including schema, using '.' as the
     * catalog separator.
     * @deprecated
     */
    @Deprecated
    public String getFullName() {
        return getFullIdentifier().getName();
    }

    public QualifiedDBIdentifier getQualifiedPath() {
        if (_fullPath  == null) {
            _fullPath = QualifiedDBIdentifier.newPath(_schemaName, _name );
        }
        return _fullPath;
    }

    public DBIdentifier getFullIdentifier() {
        return getQualifiedPath().getIdentifier();
    }

    @Override
    public File getSourceFile() {
        return _source;
    }

    @Override
    public Object getSourceScope() {
        return null;
    }

    @Override
    public int getSourceType() {
        return _srcType;
    }

    public void setSource(File source, int srcType) {
        _source = source;
        _srcType = srcType;
    }

    @Override
    public String getResourceName() {
        return getFullIdentifier().getName();
    }

    /**
     * Return the table's columns, in alphabetical order.
     */
    public Column[] getColumns() {
        if (_cols == null) {
            if (_colMap == null)
                _cols = Schemas.EMPTY_COLUMNS;
            else {
                Column[] cols = new Column[_colMap.size()];
                Iterator<Column> itr = _colMap.values().iterator();
                for (int i = 0; itr.hasNext(); i++) {
                    cols[i] = itr.next();
                    cols[i].setIndex(i);
                }
                _cols = cols;
            }
        }
        return _cols;
    }

    /**
     * Return this table's auto-assigned columns.
     */
    public Column[] getAutoAssignedColumns() {
        if (_autoAssign == null) {
            if (_colMap == null)
                _autoAssign = Schemas.EMPTY_COLUMNS;
            else {
                Collection<Column> autos = null;
                Column[] cols = getColumns();
                for (Column col : cols) {
                    if (col.isAutoAssigned()) {
                        if (autos == null)
                            autos = new ArrayList<>(3);
                        autos.add(col);
                    }
                }
                _autoAssign = (autos == null) ? Schemas.EMPTY_COLUMNS
                    : (Column[]) autos.toArray(new Column[autos.size()]);
            }
        }
        return _autoAssign;
    }

    /**
     * Return this table's relation id columns.
     */
    public Column[] getRelationIdColumns() {
        if (_rels == null) {
            if (_colMap == null)
                _rels = Schemas.EMPTY_COLUMNS;
            else {
                Collection<Column> rels = null;
                Column[] cols = getColumns();
                for (Column col : cols) {
                    if (col.isRelationId()) {
                        if (rels == null)
                            rels = new ArrayList<>(3);
                        rels.add(col);
                    }
                }
                _rels = (rels == null) ? Schemas.EMPTY_COLUMNS
                    : (Column[]) rels.toArray(new Column[rels.size()]);
            }
        }
        return _rels;
    }

    /**
     * Return the list of column names, used only for informative (error) messages.
     * @return
     */
    public String[] getColumnNames() {
        if (_colMap == null) {
            return new String[0];
        }
        return _colMap
                .values()
                .stream()
                .map(Column::getIdentifier)
                .map(DBIdentifier::getName)
                .toArray(String[]::new);
    }

    /**
     * Return the column with the given name, or null if none.
     * @deprecated
     */
    @Deprecated
    public Column getColumn(String name) {
        return getColumn(DBIdentifier.newIdentifier(name, DBIdentifierType.COLUMN, true));
    }

    private Column internalGetColumn(DBIdentifier name) {
        if (DBIdentifier.isNull(name) || _colMap == null)
            return null;
        DBIdentifier key = normalizeColumnKey(name);
        return _colMap.get(key);
    }

    public Column getColumn(DBIdentifier name) {
        return internalGetColumn(name);
    }

    private static DBIdentifier normalizeColumnKey(DBIdentifier name) {
        return DBIdentifier.removeDelimitersAndMakeUpper(name);
    }

    public Column getColumn(DBIdentifier name, boolean create) {
        return getColumn(name);
    }

    /**
     * Affirms if this table contains the column of the given name without any
     * side-effect.
     * @see Table#getColumn(String) can have side-effect of creating a column
     * for dynamic table implementation.
     * @deprecated
     */
    @Deprecated
    public boolean containsColumn(String name) {
        return containsColumn(DBIdentifier.newColumn(name), null);
    }

    public boolean containsColumn(DBIdentifier name) {
        return containsColumn(name, null);
    }

    /**
     * Affirms if this table contains the column of the given name without any
     * side-effect.
     * @param dict the current database dictionary or null.
     * @see Table#getColumn(String) can have side-effect of creating a column
     * for dynamic table implementation.
     * @deprecated
     */
    @Deprecated
    public boolean containsColumn(String name, DBDictionary dict) {
        if (name == null || _colMap == null) {
            return false;
        }
        return containsColumn(DBIdentifier.newIdentifier(name, DBIdentifierType.COLUMN, true));
    }

    public boolean containsColumn(DBIdentifier name, DBDictionary dict) {
        if (DBIdentifier.isNull(name) || _colMap == null) {
            return false;
        }
        return _colMap.containsKey(normalizeColumnKey(name));
    }

    public boolean containsColumn(Column col) {
        DBIdentifier colName = col.getIdentifier();
        if (DBIdentifier.isNull(colName) || _colMap == null) {
            return false;
        }
        DBIdentifier sName = DBIdentifier.toUpper(colName);
        Collection<Column> coll = _colMap.values();
        for (Column column : coll) {
            if (column.getIdentifier().equals(sName))
                return true;
        }

        return false;
    }

    /**
     * Add a column to the table.
     * @deprecated
     */
    @Deprecated
    public Column addColumn(String name) {
        return addColumn(DBIdentifier.newColumn(name));
    }

    public Column addColumn(DBIdentifier name) {
        addName(name, true);
        Schema schema = getSchema();
        Column col;
        if (schema != null && schema.getSchemaGroup() != null) {
            col = schema.getSchemaGroup().newColumn(name, this);
        } else {
            col = new Column(name, this);
        }
        if (_colMap == null)
            _colMap = new LinkedHashMap<>();
        _colMap.put(normalizeColumnKey(name), col);
        _cols = null;
        return col;
    }


    /**
     * Add a column with a shortened (i.e., validated) name to the table
     * @deprecated
     */
    @Deprecated
    public Column addColumn(String name, String validName) {
        return addColumn(DBIdentifier.newColumn(name), DBIdentifier.newColumn(validName));
    }

    public Column addColumn(DBIdentifier name, DBIdentifier validName) {
        addName(name, true);
        Schema schema = getSchema();
        Column col;
        if (schema != null && schema.getSchemaGroup() != null)
            col = schema.getSchemaGroup().newColumn(validName, this);
        else
            col = new Column(validName, this);
        if (_colMap == null)
            _colMap = new LinkedHashMap<>();
        _colMap.put(normalizeColumnKey(name), col);
        _cols = null;
        return col;
    }

    /**
     * Add a name to this NameSet
     * @deprecated
     */
    @Deprecated
    public void addCorrectedColumnName(String name, boolean validate) {
        addName(DBIdentifier.newColumn(name), validate);
    }

    public void addCorrectedColumnName(DBIdentifier name, boolean validate) {
        addName(name, validate);
    }


    /**
     * Remove the given column from the table.
     *
     * @return true if the column was removed, false if not in the table
     */
    public boolean removeColumn(Column col) {
        if (col == null || _colMap == null)
            return false;

        DBIdentifier sName = normalizeColumnKey(col.getIdentifier());
        Column cur = _colMap.get(sName);
        if (!col.equals(cur))
            return false;

        removeName(sName);
        _colMap.remove(sName);
        _cols = null;
        if (col.isAutoAssigned())
            _autoAssign = null;
        if (col.isRelationId())
            _rels = null;
        col.remove();
        return true;
    }

    /**
     * Import a column from another table.
     */
    public Column importColumn(Column col) {
        if (col == null)
            return null;

        Column copy = addColumn(col.getIdentifier());
        copy.setType(col.getType());
        copy.setTypeIdentifier(col.getTypeIdentifier());
        copy.setJavaType(col.getJavaType());
        copy.setNotNull(col.isNotNull());
        copy.setDefaultString(col.getDefaultString());
        copy.setSize(col.getSize());
        copy.setDecimalDigits(col.getDecimalDigits());
        copy.setAutoAssigned(col.isAutoAssigned());
        copy.setXML(col.isXML());
        return copy;
    }

    /**
     * Return the primary key for the table, if any.
     */
    public PrimaryKey getPrimaryKey() {
        return _pk;
    }

    /**
     * Set the primary key for the table.
     */
    public PrimaryKey addPrimaryKey() {
        return addPrimaryKey(DBIdentifier.NULL);
    }

    /**
     * Set the primary key for the table.
     * @deprecated
     */
    @Deprecated
    public PrimaryKey addPrimaryKey(String name) {
        return addPrimaryKey(DBIdentifier.newConstraint(name));
    }

    public PrimaryKey addPrimaryKey(DBIdentifier name) {
        Schema schema = getSchema();
        if (schema != null && schema.getSchemaGroup() != null) {
            schema.getSchemaGroup().addName(name, false);
            _pk = schema.getSchemaGroup().newPrimaryKey(name, this);
        } else
            _pk = new PrimaryKey(name, this);
        return _pk;
    }

    /**
     * Remove the primary key from this table.
     *
     * @return true if there was a pk to remove, false otherwise
     */
    public boolean removePrimaryKey() {
        boolean rem = _pk != null;
        if (rem) {
            Schema schema = getSchema();
            if (schema != null && schema.getSchemaGroup() != null)
                schema.getSchemaGroup().removeName(_pk.getIdentifier());
            _pk.remove();
        }
        _pk = null;
        return rem;
    }

    /**
     * Import a primary key; column names must match columns of this table.
     */
    public PrimaryKey importPrimaryKey(PrimaryKey pk) {
        if (pk == null)
            return null;

        PrimaryKey copy = addPrimaryKey(pk.getIdentifier());
        copy.setLogical(pk.isLogical());
        Column[] cols = pk.getColumns();
        for (Column col : cols) {
            copy.addColumn(getColumn(col.getIdentifier()));
        }
        return copy;
    }

    /**
     * Return the foreign key with the given name. If multiple foreign keys
     * have the name, the first match is returned.
     * @deprecated
     */
    @Deprecated
    public ForeignKey getForeignKey(String name) {
        return getForeignKey(DBIdentifier.newForeignKey(name));
    }

    public ForeignKey getForeignKey(DBIdentifier name) {
        ForeignKey[] fks = getForeignKeys();
        for (ForeignKey fk : fks) {
            if (name.equals(fk.getIdentifier())) {
                return fk;
            }
        }
        return null;
    }

    /**
     * Return all foreign keys for the table.
     */
    public ForeignKey[] getForeignKeys() {
        if (_fks == null) {
            if (_fkList == null)
                _fks = Schemas.EMPTY_FOREIGN_KEYS;
            else {
                ForeignKey[] fks = new ForeignKey[_fkList.size()];
                Iterator<ForeignKey> itr = _fkList.iterator();
                for (int i = 0; itr.hasNext(); i++) {
                    fks[i] = itr.next();
                    fks[i].setIndex(i);
                }
                _fks = fks;
            }
        }
        return _fks;
    }

    /**
     * Add a foreign key to the table.
     */
    public ForeignKey addForeignKey() {
        return addForeignKey(DBIdentifier.NULL);
    }

    /**
     * Add a foreign key to the table. Duplicate key names are not allowed.
     * @deprecated
     */
    @Deprecated
    public ForeignKey addForeignKey(String name) {
        return addForeignKey(DBIdentifier.newForeignKey(name));
    }

    public ForeignKey addForeignKey(DBIdentifier name) {
        Schema schema = getSchema();
        ForeignKey fk;
        if (schema != null && schema.getSchemaGroup() != null) {
            schema.getSchemaGroup().addName(name, false);
            fk = schema.getSchemaGroup().newForeignKey(name, this);
        } else
            fk = new ForeignKey(name, this);
        if (_fkList == null)
            _fkList = new ArrayList<>(3);
        _fkList.add(fk);
        _fks = null;
        return fk;
    }

    /**
     * Remove the given foreign key from the table.
     *
     * @return true if the key was removed, false if not in the table
     */
    public boolean removeForeignKey(ForeignKey fk) {
        if (fk == null || _fkList == null)
            return false;

        if (!_fkList.remove(fk))
            return false;

        Schema schema = getSchema();
        if (schema != null && schema.getSchemaGroup() != null)
            schema.getSchemaGroup().removeName(fk.getIdentifier());
        _fks = null;
        fk.remove();
        return true;
    }

    /**
     * Import a foreign key; column names must match columns of this table.
     */
    public ForeignKey importForeignKey(ForeignKey fk) {
        if (fk == null)
            return null;

        ForeignKey copy = addForeignKey(fk.getIdentifier());
        copy.setDeleteAction(fk.getDeleteAction());

        Schema schema = getSchema();
        if (schema != null && schema.getSchemaGroup() != null) {
            Column[] pks = fk.getPrimaryKeyColumns();
            Table joined = null;
            if (pks.length > 0)
                joined = schema.getSchemaGroup().findTable(pks[0].getTable());

            Column[] cols = fk.getColumns();
            for (int i = 0; i < cols.length; i++)
                copy.join(getColumn(cols[i].getIdentifier()),
                    joined.getColumn(pks[i].getIdentifier()));

            cols = fk.getConstantColumns();
            for (Column col : cols)
                copy.joinConstant(getColumn(col.getIdentifier()),
                        fk.getPrimaryKeyConstant(col));

            pks = fk.getConstantPrimaryKeyColumns();
            if (joined == null && pks.length > 0)
                joined = schema.getSchemaGroup().findTable(pks[0].getTable());
            for (Column pk : pks)
                copy.joinConstant(fk.getConstant(pk),
                        joined.getColumn(pk.getIdentifier()));
        }
        return copy;
    }

    /**
     * Return the table's indexes.
     */
    public Index[] getIndexes() {
        if (_idxs == null || _idxs.length == 0)
            _idxs = (_idxMap == null) ? Schemas.EMPTY_INDEXES : (Index[])
                _idxMap.values().toArray(new Index[_idxMap.size()]);
        return _idxs;
    }

    /**
     * Return the index with the given name, or null if none.
     * @deprecated
     */
    @Deprecated
    public Index getIndex(String name) {
        if (name == null || _idxMap == null)
            return null;
        return getIndex(DBIdentifier.newIdentifier(name, DBIdentifierType.INDEX, true));
    }

    public Index getIndex(DBIdentifier name) {
        if (name == null || _idxMap == null)
            return null;

        DBIdentifier sName = DBIdentifier.toUpper(name);
        return _idxMap.get(sName);
    }

    /**
     * Add an index to the table.
     * @deprecated
     */
    @Deprecated
    public Index addIndex(String name) {
        return addIndex(DBIdentifier.newIndex(name));
    }

    public Index addIndex(DBIdentifier name) {
        Schema schema = getSchema();
        Index idx;
        if (schema != null && schema.getSchemaGroup() != null) {
            schema.getSchemaGroup().addName(name, true);
            idx = schema.getSchemaGroup().newIndex(name, this);
        } else
            idx = new Index(name, this);
        if (_idxMap == null)
            _idxMap = new TreeMap<>();
        DBIdentifier sName = DBIdentifier.toUpper(name);
        _idxMap.put(sName, idx);
        _idxs = null;
        return idx;
    }

    /**
     * Remove the given index from the table.
     *
     * @return true if the index was removed, false if not in the table
     */
    public boolean removeIndex(Index idx) {
        if (idx == null || _idxMap == null)
            return false;

        DBIdentifier sName = DBIdentifier.toUpper(idx.getIdentifier());
        Index cur = _idxMap.get(sName);
        if (!idx.equals(cur))
            return false;

        _idxMap.remove(sName);
        Schema schema = getSchema();
        if (schema != null && schema.getSchemaGroup() != null)
            schema.getSchemaGroup().removeName(idx.getIdentifier());
        idx.remove();
        _idxs = null;
        return true;
    }

    /**
     * Import an index; column names must match columns of this table.
     */
    public Index importIndex(Index idx) {
        if (idx == null)
            return null;

        Index copy = addIndex(idx.getIdentifier());
        copy.setUnique(idx.isUnique());

        Column[] cols = idx.getColumns();
        for (Column col : cols) {
            copy.addColumn(getColumn(col.getIdentifier()));
        }
        return copy;
    }

    /**
     * Return the table's unique constraints.
     */
    public Unique[] getUniques() {
        if (_unqs == null)
            _unqs = (_unqList == null) ? Schemas.EMPTY_UNIQUES : (Unique[])
                _unqList.toArray(new Unique[_unqList.size()]);
        return _unqs;
    }

    /**
     * Return the unique constraint with the given name, or null if none.
     * @deprecated
     */
    @Deprecated
    public Unique getUnique(String name) {
        return getUnique(DBIdentifier.newConstraint(name));
    }

    public Unique getUnique(DBIdentifier name) {
        Unique[] unqs = getUniques();
        for (Unique unq : unqs)
            if (DBIdentifier.equal(name, unq.getIdentifier()))
                return unq;
        return null;
    }

    /**
     * Add a unique constraint to the table.
     * @deprecated
     */
    @Deprecated
    public Unique addUnique(String name) {
        return addUnique(DBIdentifier.newConstraint(name));
    }

    public Unique addUnique(DBIdentifier name) {
        Schema schema = getSchema();
        Unique unq;
        if (schema != null && schema.getSchemaGroup() != null) {
            schema.getSchemaGroup().addName(name, false);
            unq = schema.getSchemaGroup().newUnique(name, this);
        } else
            unq = new Unique(name, this);
        if (_unqList == null)
            _unqList = new ArrayList<>(3);
        _unqList.add(unq);
        _unqs = null;
        return unq;
    }

    /**
     * Remove the given unique constraint from the table.
     *
     * @return true if the constraint was removed, false if not in the table
     */
    public boolean removeUnique(Unique unq) {
        if (unq == null || _unqList == null)
            return false;

        if (!_unqList.remove(unq))
            return false;

        Schema schema = getSchema();
        if (schema != null && schema.getSchemaGroup() != null)
            schema.getSchemaGroup().removeName(unq.getIdentifier());
        _unqs = null;
        unq.remove();
        return true;
    }

    /**
     * Import a constraint; column names must match columns of this table.
     */
    public Unique importUnique(Unique unq) {
        if (unq == null)
            return null;

        Unique copy = addUnique(unq.getIdentifier());
        copy.setDeferred(unq.isDeferred());

        Column[] cols = unq.getColumns();
        for (Column col : cols) {
            copy.addColumn(getColumn(col.getIdentifier()));
        }
        return copy;
    }

    /**
     * Called by columns to ensure that all columns are properly indexed
     * before returning that information to the user.
     */
    void indexColumns() {
        getColumns();
    }

    /**
     * Called by foreign keys to ensure that all fks are properly indexed
     * before returning that information to the user.
     */
    void indexForeignKeys() {
        getForeignKeys();
    }

    /**
     * Called by columns when their auto increment status changes.
     */
    void changeAutoAssigned(Column col) {
        _autoAssign = null;
    }

    /**
     * Called by columns when their relation id status changes.
     */
    void changeRelationId(Column col) {
        _rels = null;
    }

    @Override
    public int compareTo(Object other) {
        DBIdentifier name = getFullIdentifier();
        DBIdentifier otherName = ((Table) other).getFullIdentifier();
        if (DBIdentifier.isNull(name) && DBIdentifier.isNull(otherName))
            return 0;
        if (DBIdentifier.isNull(name))
            return 1;
        if (DBIdentifier.isNull(otherName))
            return -1;
        return name.compareTo(otherName);
    }

    @Override
    public String toString() {
        return getFullIdentifier().getName();
    }

    public boolean hasComment() {
        return _comment != null && !_comment.equalsIgnoreCase(_name.getName());
    }

    public String getComment() {
        return _comment;
    }

    public void setComment(String comment) {
        _comment = comment;
    }

    @Override
    public int getLineNumber() {
        return _lineNum;
    }

    public void setLineNumber(int lineNum) {
        _lineNum = lineNum;
    }

    @Override
    public int getColNumber() {
        return _colNum;
    }

    public void setColNumber(int colNum) {
        _colNum = colNum;
    }
}
