blob: b5695c1f61ac354a4de04b0960817b65c7cf3b44 [file] [log] [blame]
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package com.sun.jdo.spi.persistence.generator.database;
import com.sun.jdo.spi.persistence.utility.StringHelper;
import com.sun.jdo.spi.persistence.utility.logging.Logger;
/**
* Represents how a JDBC type (i.e., one defined by java.sql.Types) is
* created in the database.
*/
class JDBCInfo {
//
// These constants are used to locate properties indicating values for
// the fields in instances of JDBCInfo.
//
//
/** Indicator that property designates the type of a mapped SQL type. */
private static final String INDICATOR_TYPE =
DatabaseGenerationConstants.INDICATOR_JDBC_TYPE;
/** Indicator that property designates nullability of mapped SQL type. */
private static final String INDICATOR_NULLABLE =
DatabaseGenerationConstants.INDICATOR_JDBC_NULLABLE;
/** Indicator that property designates the precision of mapped SQL type. */
private static final String INDICATOR_PRECISION =
DatabaseGenerationConstants.INDICATOR_JDBC_PRECISION;
/** Indicator that property designates the scale of mapped SQL type. */
private static final String INDICATOR_SCALE =
DatabaseGenerationConstants.INDICATOR_JDBC_SCALE;
/** Indicator that property designates length of a mapped SQL type. */
private static final String INDICATOR_LENGTH =
DatabaseGenerationConstants.INDICATOR_JDBC_LENGTH;
/** Indicator that a type does not have a length associated with it. */
private static final String NO_LENGTH_INDICATOR = "null";
/** Flag value which indicates that a JDBCInfo does not have a length. */
private static final Integer NO_LENGTH = new Integer(-1);
/** Logger for warning & error messages */
private static final Logger logger =
LogHelperDatabaseGenerator.getLogger();
/** Value from java.sql.Types. */
private int jdbcType;
/** True iff a column of this type is nullable; default is false. */
private boolean nullable = false;
/** Indicates precision of a fixed-point number column; default is null. */
private Integer precision = null;
/** Indicates scale of a fixed-point number column; default is null. */
private Integer scale = null;
/** Indicates length of a char, etc. column; default is null. */
private Integer length = null;
//
// Allow determining if which fields have been assigned values.
//
/** Indicates which fields in this instance have been set. */
private byte fieldsWithValues = 0;
/** Mask to indicate whether or not {@link #jdbcType} has a value. */
private static final byte MASK_JDBC_TYPE = 1 << 0;
/** Mask to indicate whether or not {@link #nullable} has a value. */
private static final byte MASK_NULLABLE = 1 << 1;
/** Mask to indicate whether or not {@link #precision} has a value. */
private static final byte MASK_PRECISION = 1 << 2;
/** Mask to indicate whether or not {@link #scale} has a value. */
private static final byte MASK_SCALE = 1 << 3;
/** Mask to indicate whether or not {@link #length} has a value. */
private static final byte MASK_LENGTH = 1 << 4;
/** Mask to access all field flags at once. */
private static final byte MASK_ALL = MASK_JDBC_TYPE | MASK_NULLABLE
| MASK_PRECISION | MASK_SCALE | MASK_LENGTH;
/**
* Constructor which initializes all fields.
* @param jdbcType See {@link jdbcType}.
* @param precision See {@link precision}.
* @param scale See {@link scale}.
* @param length See {@link length}.
* @param nullable See {@link nullable}.
*/
JDBCInfo(int jdbcType, Integer precision, Integer scale,
Integer length, boolean nullable) {
this.jdbcType = jdbcType;
this.precision = precision;
this.scale = scale;
this.length = length;
this.nullable = nullable;
fieldsWithValues = MASK_ALL;
}
/**
* Use this constructor in conjunction with multiple setValue to
* initialize an instance.
*/
JDBCInfo() { }
/**
* Sets the value of one field of this JDBCInfo.
* @param indicator Determines which field is set.
* @param value String form of the new value of a field. Must not be
* null. Empty String means to reset the field value to its default,
* except for jdbcType: That field has no default, so given an empty
* String the value of jdbcType is unchanged.
* @throws IllegalJDBCTypeException if <code>indicator</code> shows that
* we are setting a JDBC Type and <code>value</code> is not
* recognized as being a valid member of java.sql.Types.
*/
void setValue(String value, String indicator)
throws IllegalJDBCTypeException {
if (indicator.equals(INDICATOR_TYPE)) {
if (!StringHelper.isEmpty(value)) {
Integer type = MappingPolicy.getJdbcType(value);
if (null == type) {
throw new IllegalJDBCTypeException();
}
this.jdbcType = type.intValue();
this.fieldsWithValues |= MASK_JDBC_TYPE;
}
} else if (indicator.equals(INDICATOR_NULLABLE)) {
if (StringHelper.isEmpty(value)) {
this.nullable = false; // default
} else {
this.nullable = Boolean.valueOf(value).booleanValue();
}
this.fieldsWithValues |= MASK_NULLABLE;
} else if (indicator.equals(INDICATOR_PRECISION)) {
this.precision = getIntegerValue(value);
this.fieldsWithValues |= MASK_PRECISION;
} else if (indicator.equals(INDICATOR_SCALE)) {
this.scale = getIntegerValue(value);
this.fieldsWithValues |= MASK_SCALE;
} else if (indicator.equals(INDICATOR_LENGTH)) {
if (value.trim().equals(NO_LENGTH_INDICATOR)) {
this.length = NO_LENGTH;
} else {
this.length = getIntegerValue(value);
}
this.fieldsWithValues |= MASK_LENGTH;
}
}
/**
* @param s String whose Integer value is sought.
* @return the value of s as an Integer, or null if s is empty.
*/
private Integer getIntegerValue(String s) {
Integer rc = null;
if (!StringHelper.isEmpty(s)) {
rc = new Integer(s);
}
return rc;
}
//
// A note about "completeness".
// JDBCInfo instances are created one of 2 ways: By loading a .properties
// file for a database, or by a user override. In the first case, the
// JDBCInfo will have values for all relevant fields, because that is the
// way we have created the .properties files. ("relevant" here means
// that, e.g., a length value will be present if required for a field
// type for which length is relevant such as VARCHAR.)
//
// In the second case, a user override might provide only one overriden
// value, let's say length. So for a particular field name, we may know
// only that it should have a length; of course we need more. So before
// allowing access to a JDBCInfo that was created for a field, complete()
// it with a JDBCInfo that was created for that field's type.
//
// See MappingPolicy.JDBCInfo().
//
/**
* Fill in values for fields based on values in another JDBCInfo
* instance. Only those fields for which this instance does not already
* have a value are changed.
* @param other Another instance of JDBCInfo that has values which are
* used to set as-yet-unset values in this instance.
*/
// XXX For precision and scale, this is not entirely correct.
// We should check if this.<val> is set. If so, it must not be greater
// than other.<val>. In other words, the other instance's value
// overrules the value in this instance, because the other value was
// provided in the dbvendor-specific .properties file, which users must
// not override. If this instance (i.e., the user override's instance)
// specifies an invalid override, we should log a warning, warn the user,
// and use the other.<val>.
void complete(JDBCInfo other) {
if (logger.isLoggable(Logger.FINEST)) {
logger.finest("Entering JDBCInfo.complete: " // NOI18N
+ "\nthis: " + this // NOI18N
+ "\nother: " + other); // NOI18N
}
if (MASK_ALL != fieldsWithValues) {
if ((fieldsWithValues & MASK_JDBC_TYPE) == 0) {
this.jdbcType = other.jdbcType;
}
if ((fieldsWithValues & MASK_NULLABLE) == 0) {
this.nullable = other.nullable;
}
if ((fieldsWithValues & MASK_PRECISION) == 0) {
this.precision = other.precision;
}
if ((fieldsWithValues & MASK_SCALE) == 0) {
this.scale = other.scale;
}
if ((fieldsWithValues & MASK_LENGTH) == 0
|| (NO_LENGTH.equals(other.length))
|| (other.length.intValue() < this.length.intValue())) {
this.length = other.length;
}
fieldsWithValues = MASK_ALL;
}
if (logger.isLoggable(Logger.FINEST)) {
logger.finest("Leaving JDBCInfo.complete: " // NOI18N
+ "\nthis: " + this); // NOI18N
}
}
/**
* @return <code>true</code> if this instance has been assigned values
* for all fields.
*/
boolean isComplete() {
return fieldsWithValues == MASK_ALL;
}
/**
* Update this JDBCInfo with information from the other JDBCInfo.
* @param other The JDBCInfo with values that will overwrite values in
* this JDBCInfo.
*/
void override(JDBCInfo other) {
if (null != other) {
this.jdbcType = other.jdbcType;
}
}
/**
* @return true iff this instance has a value for jdbcType
*/
public boolean hasJdbcType() {
return (fieldsWithValues & MASK_JDBC_TYPE) == 1;
}
/**
* @return The JDBC corresponding to this JDBCInfo. See
* {@link java.sql.Types}.
*/
public int getJdbcType() {
return jdbcType;
}
/**
* @return <code>true</code> of columns based on this JDBCInfo should be
* nullable.
*/
public boolean getNullable() {
return nullable;
}
/**
* @return The precision of of columns based on this JDBCInfo.
*/
public Integer getPrecision() {
return precision;
}
/**
* @return The scale of of columns based on this JDBCInfo.
*/
public Integer getScale() {
return scale;
}
/**
* @return The length of of columns based on this JDBCInfo, or null if
* this JDBCInfo does not need a length (e.g. CLOB on Oracle).
*/
public Integer getLength() {
return NO_LENGTH.equals(length) ? null : length;
}
/**
* Debugging support.
* @return A String with the value of each field.
*/
public String toString() {
return "JDBCInfo:" // NOI18N
+ " jdbcType=" + jdbcType // NOI18N
+ " nullable=" + nullable // NOI18N
+ " precision=" + precision // NOI18N
+ " scale=" + scale // NOI18N
+ " length=" + length // NOI18N
+ " fieldsWithValues=0x" + Integer.toHexString(fieldsWithValues); // NOI18N
}
/**
* Used to indicate that a given JDBC Type name is not recognized.
*/
static class IllegalJDBCTypeException extends Exception {
IllegalJDBCTypeException() { }
}
}