FileDocCategorySizeDatePackage
JDBCInfo.javaAPI DocGlassfish v2 API13754Fri May 04 22:34:40 BST 2007com.sun.jdo.spi.persistence.generator.database

JDBCInfo.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

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
                || (other.length == NO_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() { }
    }
}