| /******************************************************************************* |
| * Copyright (c) 1998, 2012 Oracle. All rights reserved. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 |
| * which accompanies this distribution. |
| * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html |
| * and the Eclipse Distribution License is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * Contributors: |
| * Oracle - initial API and implementation from Oracle TopLink |
| ******************************************************************************/ |
| package org.eclipse.persistence.tools.db.model; |
| |
| import java.util.List; |
| import org.eclipse.persistence.tools.db.model.platformsmodel.DatabasePlatform; |
| import org.eclipse.persistence.tools.db.model.platformsmodel.DatabaseType; |
| import org.eclipse.persistence.tools.db.model.platformsmodel.JavaTypeDeclaration; |
| import org.eclipse.persistence.tools.db.model.spi.ExternalColumn; |
| import org.eclipse.persistence.tools.utility.StringTools; |
| import org.eclipse.persistence.tools.utility.node.Node; |
| |
| /** |
| * @version 2.6 |
| */ |
| @SuppressWarnings("nls") |
| public final class ELColumn extends ELModel { |
| |
| /** the name should never be null or empty */ |
| private volatile String name; |
| public static final String NAME_PROPERTY = "name"; |
| public static final String QUALIFIED_NAME_PROPERTY = "qualifiedName"; |
| |
| /** the database type should never be null */ |
| private DatabaseTypeHandle databaseTypeHandle; // pseudo-final |
| public static final String DATABASE_TYPE_PROPERTY = "databaseType"; |
| |
| private volatile int size; |
| public static final String SIZE_PROPERTY = "size"; |
| |
| /** sub-size is used for scale on numeric types */ |
| private volatile int subSize; |
| public static final String SUB_SIZE_PROPERTY = "subSize"; |
| |
| private volatile boolean allowsNull; |
| public static final String ALLOWS_NULL_PROPERTY = "allowsNull"; |
| |
| private volatile boolean unique; |
| public static final String UNIQUE_PROPERTY = "unique"; |
| |
| /** this is typically short-hand for 'NOT NULL' and 'UNIQUE' */ |
| private volatile boolean primaryKey; |
| public static final String PRIMARY_KEY_PROPERTY = "primaryKey"; |
| |
| /** SQL Server variants allow the IDENTITY clause */ |
| private volatile boolean identity; |
| public static final String IDENTITY_PROPERTY = "identity"; |
| |
| // ********** static methods ********** |
| |
| /** |
| * Parse the table name from a possibly "qualified" column name: |
| * "ACTG.EMPLOYEE.F_NAME" -> "ACCTG.EMPLOYEE" |
| * "ACTG..F_NAME" -> "ACCTG.." |
| * "EMPLOYEE.F_NAME" -> "EMPLOYEE" |
| * "F_NAME" -> "" |
| */ |
| public static String parseTableNameFromQualifiedName(String name) { |
| int index = name.lastIndexOf('.'); |
| if (index == -1) { |
| return StringTools.EMPTY_STRING; |
| } |
| return name.substring(0, index); |
| } |
| |
| /** |
| * Parse the column name from a possibly "qualified" column name: |
| * "ACCT.EMPLOYEE.F_NAME" -> "F_NAME" |
| * "ACCT..F_NAME" -> "F_NAME" |
| * "EMPLOYEE.F_NAME" -> "F_NAME" |
| * "F_NAME" -> "F_NAME" |
| */ |
| public static String parseColumnNameFromQualifiedName(String name) { |
| int index = name.lastIndexOf('.'); |
| if (index == -1) { |
| return name; |
| } |
| return name.substring(index + 1); |
| } |
| |
| public static boolean nameIsQualified(String columnName) { |
| return columnName.indexOf(".") != -1; |
| } |
| |
| |
| // ********** constructors ********** |
| |
| ELColumn(ELTable table, String name) { |
| super(table); |
| this.name = name; |
| } |
| |
| |
| // ********** initialization ********** |
| |
| /** |
| * the database type handle is not mapped directly |
| */ |
| @Override |
| protected void initialize() { |
| super.initialize(); |
| this.databaseTypeHandle = new DatabaseTypeHandle(this); |
| } |
| |
| /** |
| * initialize persistent state |
| */ |
| @Override |
| protected void initialize(Node parent) { |
| super.initialize(parent); |
| DatabaseType dbType = this.defaultDatabaseType(); |
| this.databaseTypeHandle.setDatabaseType(dbType); |
| this.size = dbType.requiresSize() ? dbType.getInitialSize() : 0; |
| this.subSize = 0; |
| this.allowsNull = dbType.allowsNull(); |
| this.unique = false; |
| this.primaryKey = false; |
| this.identity = false; |
| } |
| |
| |
| // ********** accessors ********** |
| |
| public ELTable getTable() { |
| return this.getParent(); |
| } |
| |
| public String getName() { |
| return this.name; |
| } |
| |
| public void setName(String name) { |
| this.getTable().checkColumnName(name); |
| Object old = this.name; |
| this.name = name; |
| if (this.attributeValueHasChanged(old, name)) { |
| this.firePropertyChanged(NAME_PROPERTY, old, name); |
| this.qualifiedNameChanged(); |
| } |
| } |
| |
| void qualifiedNameChanged() { |
| String qName = this.qualifiedName(); |
| this.firePropertyChanged(QUALIFIED_NAME_PROPERTY, qName); |
| } |
| |
| public DatabaseType getDatabaseType() { |
| return this.databaseTypeHandle.getDatabaseType(); |
| } |
| |
| public void setDatabaseType(DatabaseType databaseType) { |
| if (databaseType == null) { |
| throw new NullPointerException(); |
| } |
| Object old = this.databaseTypeHandle.getDatabaseType(); |
| this.databaseTypeHandle.setDatabaseType(databaseType); |
| this.firePropertyChanged(DATABASE_TYPE_PROPERTY, old, databaseType); |
| // align various settings with the new database type |
| if (this.attributeValueHasChanged(old, databaseType)) { |
| this.synchronizeWithNewDatabaseType(); |
| } |
| } |
| |
| public int getSize() { |
| return this.size; |
| } |
| |
| public void setSize(int size) { |
| if (( ! this.getDatabaseType().allowsSize()) && (size != 0)) { |
| throw new IllegalArgumentException("size must be 0 when size is not allowed"); |
| } |
| int old = this.size; |
| this.size = size; |
| this.firePropertyChanged(SIZE_PROPERTY, old, size); |
| } |
| |
| public int getSubSize() { |
| return this.subSize; |
| } |
| |
| public void setSubSize(int subSize) { |
| if (( ! this.getDatabaseType().allowsSubSize()) && (subSize != 0)) { |
| throw new IllegalArgumentException("sub-size must be 0 when sub-size is not allowed"); |
| } |
| int old = this.subSize; |
| this.subSize = subSize; |
| this.firePropertyChanged(SUB_SIZE_PROPERTY, old, subSize); |
| } |
| |
| public boolean allowsNull() { |
| return this.allowsNull; |
| } |
| |
| public void setAllowsNull(boolean allowsNull) { |
| if (( ! this.getDatabaseType().allowsNull()) && (allowsNull)) { |
| throw new IllegalArgumentException("allows null must be false when allows null is not allowed"); |
| } |
| boolean old = this.allowsNull; |
| this.allowsNull = allowsNull; |
| this.firePropertyChanged(ALLOWS_NULL_PROPERTY, old, allowsNull); |
| if (allowsNull) { |
| this.setPrimaryKey(false); |
| this.setIdentity(false); |
| } |
| } |
| |
| public boolean isUnique() { |
| return this.unique; |
| } |
| |
| public void setUnique(boolean unique) { |
| boolean old = this.unique; |
| this.unique = unique; |
| this.firePropertyChanged(UNIQUE_PROPERTY, old, unique); |
| if ( ! unique) { |
| this.setPrimaryKey(false); |
| } |
| } |
| |
| public boolean isPrimaryKey() { |
| return this.primaryKey; |
| } |
| |
| /** |
| * primary key is typically equivalent to |
| * 'NOT NULL' and 'UNIQUE' |
| */ |
| public void setPrimaryKey(boolean primaryKey) { |
| boolean old = this.primaryKey; |
| this.primaryKey = primaryKey; |
| this.firePropertyChanged(PRIMARY_KEY_PROPERTY, old, primaryKey); |
| if (primaryKey) { |
| this.setAllowsNull(false); |
| this.setUnique(true); |
| } |
| } |
| |
| public boolean isIdentity() { |
| return this.identity; |
| } |
| |
| /** |
| * 'IDENTITY' requires 'NOT NULL' |
| */ |
| public void setIdentity(boolean identity) { |
| if (( ! this.supportsIdentityClause()) && (identity)) { |
| throw new IllegalArgumentException("the current platform does not support the IDENTITY clause"); |
| } |
| boolean old = this.identity; |
| this.identity = identity; |
| this.firePropertyChanged(IDENTITY_PROPERTY, old, identity); |
| if (identity) { |
| this.setAllowsNull(false); |
| } |
| } |
| |
| |
| // ********** queries ********** |
| |
| public DatabasePlatform databasePlatform() { |
| return this.getTable().databasePlatform(); |
| } |
| |
| public String qualifiedName() { |
| return this.getTable().qualifiedName() + '.' + this.getName(); |
| } |
| |
| /** |
| * this is the initial value of our database type |
| */ |
| private DatabaseType defaultDatabaseType() { |
| return this.databasePlatform().defaultDatabaseType(); |
| } |
| |
| /** |
| * Returns the Java type declaration corresponding to the column's database type; |
| * this is used to generate instance variables from database columns when |
| * generating classes from tables |
| */ |
| public JavaTypeDeclaration javaTypeDeclaration() { |
| return this.getDatabaseType().javaTypeDeclaration(); |
| } |
| |
| /** |
| * Returns whether the column can be defined with an IDENTITY |
| * clause (SQL Server variants only) |
| */ |
| public boolean supportsIdentityClause() { |
| return this.getDatabase().supportsIdentityClause(); |
| } |
| |
| |
| // ********** behavior ********** |
| |
| @Override |
| protected void addChildrenTo(List<Node> children) { |
| super.addChildrenTo(children); |
| children.add(this.databaseTypeHandle); |
| } |
| |
| @Override |
| public ELTable getParent() { |
| return (ELTable)super.getParent(); |
| } |
| |
| public ELDatabase getDatabase() { |
| return getParent().getParent(); |
| } |
| |
| /** |
| * replace our database type (from our old database platform) with |
| * a database type from our new database platform that is a reasonable |
| * match |
| */ |
| void databasePlatformChanged() { |
| this.setDatabaseType(this.databasePlatform().databaseTypeFor(this.getDatabaseType())); |
| } |
| |
| private void synchronizeWithNewDatabaseType() { |
| DatabaseType dbType = this.getDatabaseType(); |
| |
| // synchronize size/sub-size |
| if (dbType.allowsSize()) { |
| if (dbType.allowsSubSize()) { |
| // leave sub-size unchanged - it will be zero unless we are converting |
| // from one numeric type to another numeric type and a "scale" was specified |
| } else { |
| this.setSubSize(0); |
| } |
| if (dbType.requiresSize()) { |
| if (this.size == 0) { |
| this.setSize(dbType.getInitialSize()); |
| } else { |
| // take the previously-assigned size |
| } |
| } else { |
| if (this.subSize == 0) { |
| // if size is not required and a sub-size was not specified, |
| // we probably want to clear out size: VARCHAR2(20) => NUMBER |
| this.setSize(0); |
| } else { |
| // we will only get here when converting from one numeric type |
| // to another numeric type and a "scale" was specified - keep |
| // the same size/sub-size combination |
| } |
| } |
| } else { |
| this.setSize(0); |
| this.setSubSize(0); |
| } |
| |
| // synchronize allows null |
| if (dbType.allowsNull()) { |
| // leave allows null unchanged |
| } else { |
| this.setAllowsNull(false); |
| } |
| |
| // IDENTITY |
| if (dbType.getPlatform().supportsIdentityClause()) { |
| // leave identity unchanged |
| } else { |
| this.setIdentity(false); |
| } |
| } |
| |
| void copySettingsFrom(ELColumn original) { |
| this.setDatabaseType(original.getDatabaseType()); |
| this.setSize(original.getSize()); |
| this.setSubSize(original.getSubSize()); |
| this.setAllowsNull(original.allowsNull()); |
| this.setUnique(original.isUnique()); |
| this.setPrimaryKey(original.isPrimaryKey()); |
| this.setIdentity(original.isIdentity()); |
| } |
| |
| // ********** DataField implementation ********** |
| |
| /** |
| * used by UI components common to O-R and O-X |
| * (mappings, locking policy, etc.) |
| */ |
| public String fieldName() { |
| return this.qualifiedName(); |
| } |
| |
| |
| // ********** importing/refreshing ********** |
| |
| /** |
| * refresh with the data from the specified "external" column |
| */ |
| void refresh(ExternalColumn externalColumn) { |
| this.setDatabaseType(this.databaseTypeFrom(externalColumn)); |
| this.setSize(this.sizeFrom(externalColumn)); |
| this.setSubSize(this.subSizeFrom(externalColumn)); |
| this.setAllowsNull(this.allowsNullFrom(externalColumn)); |
| this.setPrimaryKey(externalColumn.isPrimaryKey()); |
| // leave 'unique' unchanged - I don't think we can get it from the database |
| // 'primaryKey' will be set later by our parent table |
| } |
| |
| /** |
| * first try to get the platform-specific datatype; |
| * if that fails, get the platform-specific datatype most |
| * closely associated with the JDBC type; |
| * if that fails, get the default datatype |
| */ |
| private DatabaseType databaseTypeFrom(ExternalColumn externalColumn) { |
| try { |
| return this.databasePlatform().databaseTypeNamed(externalColumn.getTypeName()); |
| } catch (IllegalArgumentException ex) { |
| return this.databaseTypeFromJDBCTypeFrom(externalColumn); |
| } |
| } |
| |
| private DatabaseType databaseTypeFromJDBCTypeFrom(ExternalColumn externalColumn) { |
| try { |
| return this.databasePlatform().databaseTypeForJDBCTypeCode(externalColumn.getJDBCTypeCode()); |
| } catch (Exception ex) { |
| // defensive - when all else fails, use the default type |
| return this.databasePlatform().defaultDatabaseType(); |
| } |
| } |
| |
| private int sizeFrom(ExternalColumn externalColumn) { |
| if ( ! this.getDatabaseType().allowsSize()) { |
| // ignore the size in the "external" column - it cannot be used in the column's declaration |
| return 0; |
| } |
| return externalColumn.getSize(); |
| } |
| |
| private int subSizeFrom(ExternalColumn externalColumn) { |
| if ( ! this.getDatabaseType().allowsSubSize()) { |
| // ignore the sub-size in the "external" column - it cannot be used in the column's declaration |
| return 0; |
| } |
| return externalColumn.getScale(); |
| } |
| |
| private boolean allowsNullFrom(ExternalColumn externalColumn) { |
| if ( ! this.getDatabaseType().allowsNull()) { |
| // ignore the flag in the "external" column - the column cannot be null, given its current datatype |
| return false; |
| } |
| return externalColumn.isNullable(); |
| } |
| |
| // ********** displaying and printing ********** |
| |
| @Override |
| public String displayString() { |
| return this.qualifiedName(); |
| } |
| |
| public void toString(StringBuffer sb) { |
| sb.append(this.qualifiedName()); |
| } |
| } |