All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.jdo.spi.persistence.generator.database.MappingPolicy Maven / Gradle / Ivy

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2016 Oracle and/or its affiliates. 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_1_1.html
 * or packager/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 packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [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.
 */
// Portion Copyright [2024] Payara Foundation and/or affiliates

/*
 * MappingPolicy.java
 *
 * Created on Jan 14, 2003
 */

package com.sun.jdo.spi.persistence.generator.database;

import com.sun.jdo.spi.persistence.utility.logging.Logger;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Types;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import org.glassfish.common.util.StringHelper;
import org.glassfish.persistence.common.DatabaseConstants;
import org.glassfish.persistence.common.I18NHelper;
import org.glassfish.persistence.common.database.DBVendorTypeHelper;

// XXX Capitalization of acronyms such as Jdbc vs. JDBC is inconsistent
// throught out this package.

/**
 * Describes how Java classes and their fields are to be mapped to database
 * tables and columns
 */
public class MappingPolicy implements Cloneable {
    //
    // The names of many properties in our .properties files are composed of
    // different pieces, here called "bases", followed by "indicators".  The
    // idea is that a base may have multiple indicators.  In a way, they are
    // kind of like instances of structs, with each base naming an instance
    // and each indicator naming an accessor to a value in the struct.
    //
    // The concatenation of bases and indicators is just another String,
    // which you can look up in a Properties object to get a value.
    //
    // Note that some property names have more than one base concatenated.
    // See SQL92.properties for examples.
    //

    /** @see DatabaseGenerationConstants#DOT */
    static final char DOT = DatabaseGenerationConstants.DOT;

    //
    // Base names of properties which describe how class and field names are
    // to be represented in a database.
    //

    /** Base name to denote a class. */
    private static final String CLASS_BASE = "{class-name}"; //NOI18N

    /** Base name to denote a field. */
    private static final String FIELD_BASE = "{field-name}"; //NOI18N

    /** Base name to denote a relationship field. */
    private static final String RELATIONSHIP_BASE =
            "{relationship-field-name}"; //NOI18N


    /** Represents a '.' in a regular expression */
    private static String REGEXP_DOT = "\\."; // NOI18N

    /** Synonym for DatabaseGenerationConstants.INDICATOR_JDBC_PREFIX. */
    private static final String INDICATOR_JDBC_PREFIX =
        DatabaseGenerationConstants.INDICATOR_JDBC_PREFIX;

    /**
     * Base name to denote maximum length of a name in a database.  We
     * support different maximum lengths for table, column, and constraint
     * names.
     */
    private static final String INDICATOR_MAXIMUM_LENGTH =
        DatabaseGenerationConstants.INDICATOR_MAXIMUM_LENGTH;

    //
    // Indicator names for properties which describe how class and field
    // names are to be represented in a database.
    //

    /** Indicator that property is for a table name. */
    private static final String INDICATOR_TABLE_NAME =
        "table-name"; //NOI18N

    /** Indicator that property is for a column name. */
    private static final String INDICATOR_COLUMN_NAME =
        "column-name"; //NOI18N

    /** Indicator that property is for a join table name. */
    private static final String INDICATOR_JOIN_TABLE_NAME =
        "join-table-name"; //NOI18N

    /** Indicator that property is for a constraint name. */
    private static final String INDICATOR_CONSTRAINT_NAME =
        "constraint-name"; //NOI18N


    //
    // These are complete property names composed of bases and indicators.
    //

    /** Prefix of properties that denote classes. */
    private static final String CLASS_PREFIX =
        CLASS_BASE + DOT;

    /** Prefix of properties that denote classes. */
    private static final String RELATIONSHIP_PREFIX =
        CLASS_PREFIX + RELATIONSHIP_BASE + DOT;

    /** Name of property that provides default field-to-column name mapping. */
    private static final String DEFAULT_COLUMN_KEY =
        CLASS_PREFIX + FIELD_BASE + DOT + INDICATOR_COLUMN_NAME;

    /** Name of property that provides default jointable name mapping. */
    private static final String DEFAULT_JOIN_TABLE_KEY =
        RELATIONSHIP_PREFIX + INDICATOR_JOIN_TABLE_NAME;

    /** Name of property that provides default constraint name mapping. */
    private static final String DEFAULT_CONSTRAINT_KEY =
        RELATIONSHIP_PREFIX + INDICATOR_CONSTRAINT_NAME;

    /** Name of property that provides default class-to-table name mapping. */
    private static final String DEFAULT_TABLE_KEY =
        CLASS_PREFIX + INDICATOR_TABLE_NAME;


    //
    // Now, here are values of properties which indicate how table and column
    // names are to be generated.  I.e., in our .properties files, these are
    // potential values for some property names composed of the above bases
    // and indicators.
    //

    /** Property value indicating table name must be same as class name. */
    private static final String TABLE_NAME_AS_CLASSNAME =
        "{className}"; //NOI18N

    /** Property value indicating table name must be upper case. */
    private static final String TABLE_NAME_UPPERCASE =
        TABLE_NAME_AS_CLASSNAME.toUpperCase();

    /** Property value indicating table name must be uppercase and unique. */
    private static final String TABLE_NAME_HASH_UPPERCASE =
        "{HASH-CLASSNAME}"; //NOI18N

    /** Property value indicating colum name must be same as field name. */
    private static final String COLUMN_NAME_AS_FIELDNAME =
        "{fieldName}"; //NOI18N

    /** Property value indicating column name must be uppercase. */
    private static final String COLUMN_NAME_UPPERCASE =
        COLUMN_NAME_AS_FIELDNAME.toUpperCase();

    /** Property value indicating join table name must be uppercase. */
    private static final String JOIN_TABLE_NAME_UPPERCASE =
        "{CLASSNAMES}"; //NOI18N

    /** Property value indicating constraint name must be uppercase. */
    private static final String CONSTRAINT_NAME_UPPERCASE =
        "{FIELDNAMES}"; //NOI18N


    //
    // Here are indicators for properties that direct how vendor-dependent
    // SQL is generated.
    //

    /** Indicator that property is for formatting SQL */
    private static final String INDICATOR_SQL_FORMAT = "sql-format"; //NOI18N

    /** The indicator for a statement separator. */
    private static final String STATEMENT_SEPARATOR_INDICATOR =
        "statementSeparator"; // NOI18N

    /** The indicator for starting a "create table". */
    private static final String CREATE_TABLE_START_INDICATOR =
        "createTableStart"; // NOI18N

    /** The indicator for ending a "create table". */
    private static final String CREATE_TABLE_END_INDICATOR =
        "createTableEnd"; // NOI18N

    /** The indicator for "create index". Added for Symfoware support as */
    /** indexes on primary keys are mandatory */
    private static final String CREATE_INDEX_INDICATOR =
        "createIndex"; // NOI18N

    /** The indicator for starting a "drop table". */
    private static final String DROP_TABLE_INDICATOR =
        "dropTable"; // NOI18N

    /** The indicator for "add constraint". */
    private static final String ALTER_TABLE_ADD_CONSTRAINT_START_INDICATOR =
        "alterTableAddConstraintStart"; // NOI18N

    /** The indicator for "drop constraint". */
    private static final String ALTER_TABLE_DROP_CONSTRAINT_INDICATOR =
        "alterTableDropConstraint"; // NOI18N

    /** The indicator for adding a primary key constraint. */
    private static final String PRIMARY_KEY_CONSTRAINT_INDICATOR =
        "primaryKeyConstraint"; // NOI18N

    /** The indicator for adding a foreign key constraint. */
    private static final String FOREIGN_KEY_CONSTRAINT_INDICATOR =
        "foreignKeyConstraint"; // NOI18N

    /** The indicator for verbose nullability. */
    private static final String COLUMN_NULLABILITY_INDICATOR =
        "columnNullability"; // NOI18N

    /** The indicator for information used with LOB columns. */
    private static final String LOB_LOGGING_INDICATOR =
        "LOBLogging"; // NOI18N

    //
    // The remaining constants are neither bases nor indicators.
    //

    /** Prefix of column names which are primary key columns. */
    private static final String PK_PREFIX = "PK_"; //NOI18N

    /** Prefix of column names which are foreign key columns. */
    private static final String FK_PREFIX = "FK_"; //NOI18N

    /** Name of the "global" namespace. */
    private static final String GLOBAL_NAMING_SPACE = "GLOBAL"; //NOI18N

    /** Property name which indicates unique table names should be generated. */
    public static final String USE_UNIQUE_TABLE_NAMES = "use-unique-table-names"; // NOI18N

    /** Property name which indicates reserved words. */
    private static final String RESERVED_WORDS = "reserved-words";// NOI18N

    /** When appended to a reserved word, causes it to be not-reserved. */
    private static final String RESERVED_WORD_UNRESERVER = "9"; // NOI18N

    /**
     * Maximum length of the counter used to create unique names with a
     * numeric id.  Note that this length includes a NAME_SEPARATOR, so that
     * we allow for 3 digits total.
     */
    private static final int MAX_LEN_COUNTER = 4;

    /** Number of chars to change a reserved word into * unreserved. */
    private static final int MAX_LEN_RESERVED = 1;

    /**
     * Name of subdirectory in which db vendor - specific properties files
     * are located.
     */
    private static final String PROPERTY_FILE_DIR =
        "com/sun/jdo/spi/persistence/generator/database/"; // NOI18N

    /** Extension used by properties files. */
    private static final String PROPERTY_FILE_EXT = ".properties"; // NOI18N

    //
    // The above are all constants; below things get "interesting".
    //


    /** This is the set of all default properties. */
    private static final Properties defaultProps = new Properties();

    //
    // XXX Consider getting these String-Integer and Integer-String maps into
    // SQLTypeUtil, which is in the dbschema module.  Or not: we support only a
    // subset of java.sql.types, and (presumably) the dbschema module would
    // provide mappings for all types.
    //
    // Note also that is code in MappingGenerator of a "SQLTypeUtil" nature.
    //

    /**
     * Map from String names to the Integer-boxed values from
     * java.sql.Types.
     */
    private static final Map jdbcTypes = new HashMap();

    /**
     * Maps from Integer-boxed values from java.sql.Types to String names.
     */
    private static final Map jdbcTypeNames = new HashMap();


    /**
     * Global counter for creating unique names in each of the namespaces.
     * Note that a single counter is used across all namespaces.
     */
    private int counter = 0;

    /**
     * Map from namespaces to Set of names defined in each namespace.  Used
     * to ensure uniqueness within namespaces.
     */
    private Map namespaces = new HashMap();

    /**
     * Indicates whether or not generated table names should include a
     * unique value as part of their names.
     */
    private boolean uniqueTableName = false;

    /**
     * Set of reserved words for a particular policy.
     */
    private final Set reservedWords = new TreeSet();

    /**
     * Set of reserved words for the default database.
     */
    private static Set defaultReservedWords;

    /**
     * Map from the string names of the java types (e.g. "java.lang.String")
     * to a JDBCInfo of information about the corresponding java.sql.Types
     * type.  Different for different dbvendor types, but the same instance,
     * per dbvendor, is shared by all MappingPolicy instances.
     */
    private final Map dbJdbcInfoMap = new HashMap();

    /**
     * Similar to {@link #dbJdbcInfoMap}, but is reinitialized by each
     * clone().  Contains user-provided overrides of the information in
     * dbjdbcInfoMap.
     */
    private Map userJdbcInfoMap = new HashMap();

    /**
     * Map from a boxed value based on fields in java.sql.Types to the String
     * name of a SQL type.
     */
    private final Map sqlInfo = new HashMap();


    //
    // Maximum lengths for table, column, and constraint names are
    // vendor-specific.
    //

    /** Maximum length of the name of a table. */
    private int tableNameMaxLength;

    /** Maximum length of the name of a column. */
    private int columnNameMaxLength;

    /** Maximum length of the name of a  constraint. */
    private int constraintNameMaxLength;


    //
    // Strings to represent vendor-specific SQL that starts a table,
    // separates statements, etc.
    //

    /** The SQL for a statement separator. */
    private String statementSeparator;

    /** The SQL for starting a "create table". */
    private String createTableStart;

    /** The SQL for ending a "create table". */
    private String createTableEnd;

    /** The SQL for "create index". */
    private String createIndex;

    /** The SQL for "drop table". */
    private String dropTable;

    /** The SQL for "add constraint". */
    private String alterTableAddConstraintStart;

    /** The SQL for "drop constraint". */
    private String alterTableDropConstraint;

    /** The SQL for adding a primary key constraint. */
    private String primaryKeyConstraint;

    /** The SQL for adding a foreign key constraint. */
    private String foreignKeyConstraint;

    /** The SQL for indicating column nullability */
    private String columnNullability;

    /** The SQL for indicating LOB column logging */
    private String lobLogging = "";

    /**
     * Map from the encoded name of a policy to its value.  For example, a
     * class name's naming policy would be encoded as
     * ".table-name".
     */
    // XXX Consider renaming this.
    private final Map namingPolicy = new HashMap();

    /** Map from database vendor names to instances of MappingPolicy. */
    private static final Map instances = new HashMap();

     /** Logger for warning & error messages */
    private static final Logger logger =
            LogHelperDatabaseGenerator.getLogger();

    /** I18N message handler */
    private final static ResourceBundle messages =
            I18NHelper.loadBundle(MappingPolicy.class);

    //
    // Initialize the JDBC String to Integer map and the default (SQL92)
    // MappingPolicy.
    //

    // XXX Why initialize the SQL policy, when there's a good chance it won't
    // ever be used?  Do we really want to support unrecognized databases?
    // See comment in getMappingPolicy.  The default properties, on the other
    // hand, *do* need to be loaded.

    // XXX We need to decide what happens when an unrecognized dbvendorname
    // is given: Error?  Warning, continue running?
    static {
        // Initialize jdbcType map.
        jdbcTypes.put("BIGINT", new Integer(Types.BIGINT)); // NOI18N
        jdbcTypes.put("BIT", new Integer(Types.BIT)); // NOI18N
        jdbcTypes.put("BLOB", new Integer(Types.BLOB)); // NOI18N
        jdbcTypes.put("CHAR", new Integer(Types.CHAR)); // NOI18N
        jdbcTypes.put("CLOB", new Integer(Types.CLOB)); // NOI18N
        jdbcTypes.put("DATE", new Integer(Types.DATE)); // NOI18N
        jdbcTypes.put("DECIMAL", new Integer(Types.DECIMAL)); // NOI18N
        jdbcTypes.put("DOUBLE", new Integer(Types.DOUBLE)); // NOI18N
        jdbcTypes.put("INTEGER", new Integer(Types.INTEGER)); // NOI18N
        jdbcTypes.put("LONGVARBINARY", new Integer(Types.LONGVARBINARY)); // NOI18N
        jdbcTypes.put("LONGVARCHAR", new Integer(Types.LONGVARCHAR)); // NOI18N
        jdbcTypes.put("NULL", new Integer(Types.NULL)); // NOI18N
        jdbcTypes.put("REAL", new Integer(Types.REAL)); // NOI18N
        jdbcTypes.put("SMALLINT", new Integer(Types.SMALLINT)); // NOI18N
        jdbcTypes.put("TIME", new Integer(Types.TIME)); // NOI18N
        jdbcTypes.put("TIMESTAMP", new Integer(Types.TIMESTAMP)); // NOI18N
        jdbcTypes.put("TINYINT", new Integer(Types.TINYINT)); // NOI18N
        jdbcTypes.put("VARCHAR", new Integer(Types.VARCHAR)); // NOI18N

        jdbcTypeNames.put(new Integer(Types.BIGINT), "BIGINT"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.BIT), "BIT"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.BLOB), "BLOB"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.CHAR), "CHAR"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.CLOB), "CLOB"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.DATE), "DATE"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.DECIMAL), "DECIMAL"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.DOUBLE), "DOUBLE"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.INTEGER), "INTEGER"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.LONGVARBINARY), "LONGVARBINARY"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.LONGVARCHAR), "LONGVARCHAR"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.NULL), "NULL"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.REAL), "REAL"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.SMALLINT), "SMALLINT"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.TIME), "TIME"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.TIMESTAMP), "TIMESTAMP"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.TINYINT), "TINYINT"); // NOI18N
        jdbcTypeNames.put(new Integer(Types.VARCHAR), "VARCHAR"); // NOI18N

        try {

            // Create and load the default mapping policy.
            new MappingPolicy();

        } catch (Throwable ex) {
            logger.log(Logger.SEVERE,
                       I18NHelper.getMessage(
                               messages,
                               "EXC_MappingPolicyNotFound",  //NOI18N
                               DBVendorTypeHelper.DEFAULT_DB));
        }
    }

    /**
     * Create the default MappingPolicy instance.
     */
    // This should be invoked only once per JVM.  See the class static
    // block of code above.
    private MappingPolicy() throws IOException {
        load(getPropertyFileName(DBVendorTypeHelper.DEFAULT_DB),
             defaultProps, false);
        init(defaultProps);

        // The DEFAULT_DB has reserved words for the default database type.
        // Other databases can access those same values through the
        // defaultReservedWords set.
        defaultReservedWords = reservedWords;

        instances.put(DBVendorTypeHelper.DEFAULT_DB, this);

        if (logger.isLoggable(Logger.FINEST)) {
            logger.finest("new MappingPolicy():\n" + toString()); // NOI18N
        }
    }

    /**
     * Create a MappingPolicy for the named database type.
     * @param databaseType Name of the database for which a MappingPolicy is
     * created.  The name must conform to one of the .properties files in
     * this package.
     */
    // This should be invoked only once per databaseType per JVM.  See
    // {@link #getMappingPolicy}.
    private MappingPolicy(String databaseType) throws IOException {
        Properties mergedProp = new Properties(defaultProps);
        load(getPropertyFileName(databaseType), mergedProp, false);
        init(mergedProp);
        instances.put(databaseType, this);

        if (logger.isLoggable(Logger.FINEST)) {
            logger.finest("new MappingPolicy(" // NOI18N
                + databaseType + "):\n" + toString()); // NOI18N
        }
    }

    /**
     * Returns a vendor-specifc MappingPolicy instance.  This method always
     * returns a copy (clone) of the known MappingPolicy to allow for
     * user-specific overrides.
     * @param databaseType a database vendor name as a String.
     * @return MappingPolicy instance corresponding to the provided
     * database vendor name.
     * @throws IOException if there are problems reading the vendor-
     * specific mappinng policy file
     */
    public synchronized static MappingPolicy getMappingPolicy(
           String databaseType) throws IOException {

        if (logger.isLoggable(Logger.FINE)) {
            logger.fine("get MappingPolicy"+databaseType); // NOI18N
        }

        MappingPolicy mappingPolicy = null;
        try {
            if (databaseType == null) {
                databaseType = DBVendorTypeHelper.DEFAULT_DB;
                // XXX FIXME Need to log a warning and report to user that we
                // are *not* using databaseType given, that we are using
                // SQL92 instead, and provide list of recognized names.
            }
            mappingPolicy = (MappingPolicy) instances.get(databaseType);
            if (mappingPolicy == null) {
                mappingPolicy = new MappingPolicy(databaseType);
            }
            mappingPolicy = (MappingPolicy) mappingPolicy.clone();
        } catch (CloneNotSupportedException ec) {
            // ignore it because it will not happen
        }
        return mappingPolicy;
    }

    /**
     * Clones the vendor-specific policy for generator session.  Replace the
     * namespaces map in the clone, so that each instance has its own.
     * @return clone of this MappingPolicy
     * @throws CloneNotSupportedException never thrown
     */
    protected Object clone() throws CloneNotSupportedException {
        MappingPolicy mappingPolicyClone = (MappingPolicy) super.clone();
        mappingPolicyClone.namespaces = new HashMap();
        mappingPolicyClone.uniqueTableName = false;
        mappingPolicyClone.userJdbcInfoMap = new HashMap();
        return mappingPolicyClone;
    }

    /**
     * Initializes the given properties from the given resourceName.
     * @param resourceName Name of the resource to open, expected to contain
     * properties in text form.
     * @param properties Properties that are to be loaded.
     * @param override If true, treat resourceName as a filename
     * from which to read; if false, treat resourceName as the
     * name of a resource accessed via the class loader which loaded the
     * MappingPolicy class.
     */
    private synchronized void load(
            final String resourceName, Properties properties, boolean override)
            throws IOException {

        if (logger.isLoggable(Logger.FINE)) {
            logger.fine("load resource:" + resourceName); // NOI18N
        }

        try (InputStream bin = new BufferedInputStream(getInputStream(resourceName, override))) {
            properties.load(bin);
            if (logger.isLoggable(Logger.FINE)) {
                logger.fine("load "+resourceName + " successfuly"); // NOI18N
            }
        }
    }

    private InputStream getInputStream(String resourceName, boolean override) throws IOException {
        InputStream in;
        if (override) {
            in = new FileInputStream(resourceName);
        } else {
            final ClassLoader loader =
                    MappingPolicy.class.getClassLoader();
            if (loader != null) {
                in = loader.getResourceAsStream(resourceName);
            } else {
                in = ClassLoader.getSystemResourceAsStream(resourceName);
            }


            if (in == null) {
                throw new IOException(I18NHelper.getMessage(messages,
                        "EXC_ResourceNotFound", resourceName));// NOI18N
            }
        }
        return in;
    }

    /**
     * Resets the namespaces and counter.
     */
    // XXX Consider renaming to "reset".
    void resetCounter() {
        namespaces.clear();
        userJdbcInfoMap.clear();
        counter = 0;
    }

    /**
     * Sets user-provided policy to that provided in the given properties.
     * @param props Properties which override built in defaults.
     */
    public void setUserPolicy(Properties props) {
        if (null != props) {

            // Look for and set JDBCInfo entries.  Use Enumeration instead of
            // iterator because former gets default values while latter does
            // not.
            for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
                String name = (String) e.nextElement();
                String value = props.getProperty(name);

                if (name.equals(USE_UNIQUE_TABLE_NAMES)) {
                    if (! StringHelper.isEmpty(value)) {
                        uniqueTableName = Boolean.parseBoolean(value);
                    }
                    continue;
                }

                StringTokenizer nameParser =
                    new StringTokenizer(name, String.valueOf(DOT));

                // Get the last element from key which is separated by DOT.
                String indicator = null;
                while (nameParser.hasMoreTokens()) {
                    indicator = nameParser.nextToken();
                }

                if (indicator != null && indicator.startsWith(INDICATOR_JDBC_PREFIX)) {
                    setJDBCInfoEntry(userJdbcInfoMap, name, value, indicator);
                } else {
                    if (logger.isLoggable(Logger.INFO)) {
                        logger.info(
                                I18NHelper.getMessage(
                                        messages,
                                        "MSG_UnexpectedUserProp", // NOI18N
                                        name, value)); // NOI18N
                    }
                }
            }
        }
    }

    /**
     * Sets whether or not unique table names should be generated.
     * @param uniqueTableName If true, tables names will be
     * unique.
     */
    public void setUniqueTableName(boolean uniqueTableName) {
        this.uniqueTableName = uniqueTableName;
    }

    /**
     * Returns the String name of the SQL type for a given JDBC type.
     * @param jdbcType One of the values from java.sql.Types.
     * @return Name of SQL type corresponding to given jdbcType.
     */
    public String getSQLTypeName(int jdbcType) {
        String rc = null;

        // The name is in sqlInfo if it was loaded from one of our
        // vendor-specific properties files.
        Object o = sqlInfo.get(Integer.valueOf(jdbcType));
        if (null != o) {
            rc = (String) o;
        } else {
            // Otherwise, user has overriden, e.g. java.lang.String -> CLOB.
            rc = getJdbcTypeName(jdbcType);
        }

        return rc;
    }

    /**
     * Returns JDBC type information corresponding to the given field name
     * and type.  If fieldName is null or there is no
     * fieldName - specific information already existing, the default for the
     * given fieldType is returned.  Otherwise, information specific to the
     * fieldName is returned.  Note: It is possible to have a field
     * in a class have the same name as another class; this mechanism is not
     * robust in that case:
     * 
     * class Foo { }
     *
     * class MyFoo {
     *     Foo Foo = new Foo();
     * }
     * 
* We think this obfuscation unlikely to occur. * @param fieldName Name of field for which information is needed. May * be null, in which case only fieldType is used to determine returned * information. * @param fieldType Name of a Java type. * @return JDBCInfo representing the field or type. */ public JDBCInfo getJDBCInfo(String fieldName, String fieldType) { JDBCInfo rc = null; if (logger.isLoggable(Logger.FINEST)) { logger.finest("Entering MappingPolicy.getJDBCInfo: " // NOI18N + fieldName + ", " + fieldType); // NOI18N } if (null != fieldName) { // If fieldName is given, try to find a JDBCInfo using that name. // Looking up fieldName only makes sense in userJdbcInfoMap // which contains the user's overrides. rc = (JDBCInfo) userJdbcInfoMap.get(fieldName); if (null != rc && (! rc.isComplete())) { // There is an override for the field named fieldName, but // it is not complete, i.e., not all possible information // about the field was provided in the user override. // // Choose a JDBCInfo to use to complete the information in rc. // If the user override specifies a type and there is // information about that type for the database, use that. // Otherwise, use the given fieldType. JDBCInfo ji = null; if (rc.hasJdbcType()) { ji = getdbJDBCInfo(rc.getJdbcType()); } if (null == ji) { ji = getdbJDBCInfo(fieldType); } // Fill in the rest of the fields in rc with values from ji. rc.complete(ji); } } if (null == rc) { // Either fieldName is null, or there is no JDBCInfo specific to // fieldName, so use fieldType. rc = getdbJDBCInfo(fieldType); } // If dbJdbcInfoMap has an entry for rc's jdbc type, replace rc's jdbc // type with the result of the mapping. This allows, for example, a // user to specify that a field should be represented by a CLOB, when // the database and/or driver do not support that but do support // LONGVARCHAR (e.g. Sybase). JDBCInfo ji = getdbJDBCInfo(rc.getJdbcType()); rc.override(ji); if (logger.isLoggable(Logger.FINEST)) { logger.finest("Leaving MappingPolicy.getJDBCInfo: " // NOI18N + fieldName + ", " + fieldType // NOI18N + " => " + rc); // NOI18N } return rc; } /** * Gets the JDBCInfo corresponding to the type in the given JDBCInfo. * I.e., it maps from one JDBCInfo to another on the basis of their * types. * @param ji JDBCInfo * @return a JDBCInfo * @throws IllegalArgumentException if type is not recognized * as being a valid member of java.sql.Types. Note that only a subset of * the types in java.sql.Types are recognized. */ private JDBCInfo getdbJDBCInfo(int jdbcType) { String typename = getJdbcTypeName(jdbcType); return (JDBCInfo) dbJdbcInfoMap.get(typename); } /** * Gets the JDBCInfo for the given fieldType * @param fieldType Name of the type of a field * @return a JDBCInfo for the given fieldType */ private JDBCInfo getdbJDBCInfo(String fieldType) { JDBCInfo rc = (JDBCInfo) dbJdbcInfoMap.get(fieldType); if (null == rc) { // There is also nothing provided for the field's // type, so use a BLOB. rc = (JDBCInfo) dbJdbcInfoMap.get("BLOB"); // NOI18N } return rc; } /** * Returns the boxed form of the java.sql.Types value corresponding to the * given name. * @param jdbcStringType Name of the value to return. * @return Value from java.sql.Types, wrapped into an Integer, or null if * jdbcTypeName is not that of a recognized JDBC type. */ static Integer getJdbcType(String jdbcTypeName) { return (Integer) jdbcTypes.get(jdbcTypeName.toUpperCase()); } /** * Provides a String that can be recognized as a policy to override the * default length of a field. * @param className name of a class * @param fieldName name of a field in that class * @return a String that can be used as the name of a length override * for a field in a class. */ public static String getOverrideForLength( String className, String fieldName) { return className + DOT + fieldName + DOT + DatabaseGenerationConstants.INDICATOR_JDBC_LENGTH; } /** * Provides a String that can be recognized as a policy to override the * default nullability of a field. * @param className name of a class * @param fieldName name of a field in that class * @return a String that can be used as the name of a nullability override * for a field in a class. */ public static String getOverrideForNullability( String className, String fieldName) { return className + DOT + fieldName + DOT + DatabaseGenerationConstants.INDICATOR_JDBC_NULLABLE; } /** * Provides a String that can be recognized as a policy to override the * default precision of a field. * @param className name of a class * @param fieldName name of a field in that class * @return a String that can be used as the name of a precision override * for a field in a class. */ public static String getOverrideForPrecision( String className, String fieldName) { return className + DOT + fieldName + DOT + DatabaseGenerationConstants.INDICATOR_JDBC_PRECISION; } /** * Provides a String that can be recognized as a policy to override the * default scale of a field. * @param className name of a class * @param fieldName name of a field in that class * @return a String that can be used as the name of a scale override * for a field in a class. */ public static String getOverrideForScale( String className, String fieldName) { return className + DOT + fieldName + DOT + DatabaseGenerationConstants.INDICATOR_JDBC_SCALE; } /** * Provides a String that can be recognized as a policy to override the * default type of a field. * @param className name of a class * @param fieldName name of a field in that class * @return a String that can be used as the name of a type override * for a field in a class. */ public static String getOverrideForType( String className, String fieldName) { return className + DOT + fieldName + DOT + DatabaseGenerationConstants.INDICATOR_JDBC_TYPE; } /** * Provide the String name of a JDBC type, as per java.sql.Types. * @param type A value from java.sql.Types * @return the String name corresponding to type * @throws IllegalArgumentException if type is not recognized * as being a valid member of java.sql.Types. Note that only a subset of * the types in java.sql.Types are recognized. */ public static String getJdbcTypeName(int type) throws IllegalArgumentException { String rc = (String) jdbcTypeNames.get(Integer.valueOf(type)); if (null == rc) { throw new IllegalArgumentException(); } return rc; } // // In the getZZZName methods below, we lookup a policy and determine a // name based on that policy. Note that the keys used for lookup are // created from the given name(s), so that the result of looking up a // policy might actually be the name that is returned. // /** * Returns the name of a table for a given class, as per current policy. * @param name Basis for what the returned table should be named, for * example the unqualified name of a class. * @param uniqueName Used if the current policy is to return a unique * name. Client must provide a name that is unique to them. * @return Name to be used for table. Regardless of the current policy, * the name is different from other names returned during the current run * of {@link DatabaseGenerator#generate}. */ // XXX FIXME: If the user needs to provide a unique name, why do we // invoke getUniqueGlobalName on it? public String getTableName(String name, String uniqueName) { StringBuilder key = new StringBuilder(name).append(DOT).append(INDICATOR_TABLE_NAME); String rc = (String)namingPolicy.get(key.toString()); if (rc == null) { rc = (String)namingPolicy.get(DEFAULT_TABLE_KEY); } if (uniqueTableName) { rc = TABLE_NAME_HASH_UPPERCASE; } if (rc.equals(TABLE_NAME_UPPERCASE)) { rc = name.toUpperCase(); } else if (rc.equals(TABLE_NAME_AS_CLASSNAME)) { rc = name; } else if (rc.equals(TABLE_NAME_HASH_UPPERCASE)) { rc = uniqueName.toUpperCase(); } return getUniqueGlobalName(rc, tableNameMaxLength); } /** * Returns the name of a column for a given field in a given class. The * column name will be unique within the table. * @param className Name of the class containing the field. * @param fieldName Name of the field for which a column name is returned. * @param tableName Name of the table in which the column name is created. * @return Name of a column that is unique within the named table. */ public String getColumnName(String className, String fieldName, String tableName) { // Get column naming policy based on className and fieldName StringBuilder key = new StringBuilder(className) .append(DOT).append(fieldName) .append(DOT).append(INDICATOR_COLUMN_NAME); String rc = (String)namingPolicy.get(key.toString()); if (rc == null) { // No fieldName specific policy, so use default for className key = new StringBuilder(className) .append(DOT).append(FIELD_BASE) .append(DOT).append(INDICATOR_COLUMN_NAME); rc = (String)namingPolicy.get(key.toString()); } if (rc == null) { // No overriding policy, so use overall default. rc = (String)namingPolicy.get(DEFAULT_COLUMN_KEY); } if (rc.equals(COLUMN_NAME_UPPERCASE)) { rc = fieldName.toUpperCase(); } else if (rc.equals(COLUMN_NAME_AS_FIELDNAME)) { rc = fieldName; } return getUniqueLocalName(rc, tableName, columnNameMaxLength); } /** * Returns the name of the column which represents a foreign key in the * named table. * @param tableName Name of the table in which the FK column will be * created. * @param columnName Name of PK column in referenced table. * @return Name of the FK column in the named table. */ // XXX Does this really need to be public? // XXX Rename to getFKColumnName public String getConstraintColumnName(String tableName, String columnName) { return getUniqueLocalName( new StringBuilder(tableName) .append(DatabaseConstants.NAME_SEPARATOR) .append(columnName).toString(), tableName, columnNameMaxLength); } /** * Returns the name of a constraint corresponding to the named * relationship. * @param relName Name of a relationship. * @param uniqueId Id that can be appened to relName to distinguish it * from other relNames in the database. Will be appended only if * {@link #uniqueTableName} is true. * @return Name of a constraint. */ public String getConstraintName(String relName, String uniqueId) { String rc = (String)namingPolicy.get(DEFAULT_CONSTRAINT_KEY); if (rc.equals(CONSTRAINT_NAME_UPPERCASE)) { rc = FK_PREFIX + relName.toUpperCase(); } if (uniqueTableName) { rc += uniqueId; } rc = getUniqueGlobalName(rc, constraintNameMaxLength); if (logger.isLoggable(Logger.FINER)) { logger.finer("MappingPolicy.getConstraintName: " // NOI8N + relName + " -> " + rc); // NOI18N } return rc; } /** * Returns the name of a PK constraint, unique-ified as required. * @param tableName Name of a table on which a constraint is to be placed. * @return Name of a constraint on named table. */ public String getPrimaryKeyConstraintName(String tableName) { return getUniqueGlobalName(PK_PREFIX + tableName, constraintNameMaxLength); } /** * Returns the name of a join table which joins the tables that correspond * to the two named classes. * @param className1 Name of one class to join. * @param className2 Name of the other class to join. * @return Name of a join table. */ public String getJoinTableName(String className1, String className2) { String rc = (String)namingPolicy.get(DEFAULT_JOIN_TABLE_KEY); if (rc.equals(JOIN_TABLE_NAME_UPPERCASE)) { rc = (className1 + className2).toUpperCase(); } return getUniqueGlobalName(rc, tableNameMaxLength); } /** * Return a unique name for a column. The column will be unique within * the named table. * @param colName Name of the column * @param tableName Name of the table. * @return A unique name for colName within tableName. */ private String getUniqueLocalName( String colName, String tableName, int maxLen) { return getUniqueName(colName, tableName, maxLen); } /** * Return a unique name for the given name. It will be "globally" unique * between invocations of method {@link #resetCounter}; the first use of * a MappingPolicy instance is "reset". * @param name Name for which a unique name is returned. * @return A unique name for given name. */ private String getUniqueGlobalName(String name, int maxLen) { return getUniqueName(name, GLOBAL_NAMING_SPACE, maxLen); } /** * Return a unique name for name. It will be unique within the given * namespace. * @param name Name for which a unique name is returned. * @param space Namespace in which the returned name is unique. * @return A unique name for given name. */ private String getUniqueName(String name, String namespace, int maxLen) { String rc = name; // Reserve MAX_LEN_COUNTER characters for unique-ifying digits. maxLen -= MAX_LEN_COUNTER; // Name cannot be more than maxLen chars long. if (name.length() > maxLen) { rc = name.substring(0, maxLen); } // Convert to upper case for uniqueing comparisons below. String nameUpper = rc.toUpperCase(); // Ensure the name we create is not a reserved word by comparing // nameUpper against reserved words. if (defaultReservedWords.contains(nameUpper) || reservedWords.contains(nameUpper)) { // Append a character that is not used as the last char of any // existing reserved words. Make sure we have space for this plus // for any uniqueing below. Length-limit both rc and nameUpper, so // that the value in the namespace and the end result have the same // length. maxLen -= MAX_LEN_RESERVED; if (rc.length() > maxLen) { // Limit nameUpper as well as rc because we need to do uniqueing // in a case-insensitve fashion. nameUpper = nameUpper.substring(0, maxLen); rc = rc.substring(0, maxLen); } nameUpper += RESERVED_WORD_UNRESERVER; rc += RESERVED_WORD_UNRESERVER; } Set names = (Set) namespaces.get(namespace); if (names == null) { // Name is first entry in namespace, therefore already unique, no // need to append counter. names = new HashSet(); names.add(nameUpper); namespaces.put(namespace, names); } else if (names.contains(nameUpper)) { // Name is already in namespace, so make a different name by // appending a count to given name. counter++; rc += DatabaseConstants.NAME_SEPARATOR + counter; } else { // Add new name to namespace. names.add(nameUpper); } return rc; } // // Accessors for SQL formatting Strings. // /** * @return the SQL for a statement separator. */ String getStatementSeparator() { return statementSeparator; } /** * @return the SQL for starting a "create table". */ String getCreateTableStart() { return createTableStart; } /** * @return the SQL for ending a "create table". */ String getCreateTableEnd() { return createTableEnd; } /** * @return the SQL for "create index". */ String getCreateIndex() { return createIndex; } /** * @return the SQL for a "drop table". */ String getDropTable() { return dropTable; } /** * @return the SQL for "add constraint". */ String getAlterTableAddConstraintStart() { return alterTableAddConstraintStart; } /** * @return the SQL for "drop constraint". */ String getAlterTableDropConstraint() { return alterTableDropConstraint; } /** * @return the SQL for adding a primary key constraint. */ String getPrimaryKeyConstraint() { return primaryKeyConstraint; } /** * @return the SQL for adding a foreign key constraint. */ String getForeignKeyConstraint() { return foreignKeyConstraint; } /** * @return the SQL for indicating column nullability */ String getColumnNullability() { return columnNullability; } /** * @return the SQL for indicating LOB column logging */ String getLobLogging() { return lobLogging; } // // This method and the 4 subsequent ones initialize a MappingPolicy // instance from a give Properties object. // /** * Initialize this MappingPolicy as per the values in the given * properties. * @param props Properties for initializing this MappingPolicy */ private void init(Properties props) { // Use Enumeration instead of iterator because former gets default // values while latter does not. for (Enumeration e = props.propertyNames(); e.hasMoreElements();) { String name = (String) e.nextElement(); String value = props.getProperty(name); if (name.equals(RESERVED_WORDS)) { initReservedWords(value); continue; } // The indicator is the last DOT-separated substring in name. String indicator = null; StringTokenizer nameParser = new StringTokenizer(name, String.valueOf(DOT)); while (nameParser.hasMoreTokens()) { indicator = nameParser.nextToken(); } if (indicator != null) { if (indicator.equals(INDICATOR_SQL_FORMAT)) { setSqlFormatEntry(name, value); } else if (indicator.startsWith(INDICATOR_JDBC_PREFIX)) { setJDBCInfoEntry(dbJdbcInfoMap, name, value, indicator); } else if (indicator.equals(INDICATOR_MAXIMUM_LENGTH)) { setLengthEntry(name, value); } else if (indicator.equals(INDICATOR_TABLE_NAME) || indicator.equals(INDICATOR_COLUMN_NAME) || indicator.equals(INDICATOR_JOIN_TABLE_NAME) || indicator.equals(INDICATOR_CONSTRAINT_NAME)) { setNamingEntry(name, value); } else { setSQLInfoEntry(name, value); } } else { setSQLInfoEntry(name, value); } } } /** * Initializes the reservedWords field. * @param res Comma-separated list of reserved words. */ private void initReservedWords(String res) { StringTokenizer st = new StringTokenizer(res, ","); while (st.hasMoreTokens()) { reservedWords.add(st.nextToken().trim()); } } // // These methods each set a value in one of our maps. // /** * Sets a SQL formatting property in this MappingPolicy. * @param name Name of the policy property, including its indicator. * @param value Value to be bound to that property. */ private void setSqlFormatEntry(String name, String value) { if (value != null) { if (name.startsWith(STATEMENT_SEPARATOR_INDICATOR)) { statementSeparator = value; } else if (name.startsWith(CREATE_TABLE_START_INDICATOR)) { createTableStart = value; } else if (name.startsWith(CREATE_TABLE_END_INDICATOR)) { createTableEnd = value; } else if (name.startsWith(CREATE_INDEX_INDICATOR)) { createIndex = value; } else if (name.startsWith(DROP_TABLE_INDICATOR)) { dropTable = value; } else if (name.startsWith(ALTER_TABLE_ADD_CONSTRAINT_START_INDICATOR)) { alterTableAddConstraintStart = value; } else if (name.startsWith(ALTER_TABLE_DROP_CONSTRAINT_INDICATOR)) { alterTableDropConstraint = value; } else if (name.startsWith(PRIMARY_KEY_CONSTRAINT_INDICATOR)) { primaryKeyConstraint = value; } else if (name.startsWith(FOREIGN_KEY_CONSTRAINT_INDICATOR)) { foreignKeyConstraint = value; } else if (name.startsWith(COLUMN_NULLABILITY_INDICATOR)) { columnNullability = value; } else if (name.startsWith(LOB_LOGGING_INDICATOR)) { lobLogging = value; } } } /** * Sets a JDBC property in this MappingPolicy. * @param Map into which property is set. * @param name Name of the policy property, including its indicator. * @param indicator The indicator, alone, which should start with * "jdbc-". * @param value Value to be bound to that property. */ private void setJDBCInfoEntry( Map jdbcInfoMap, String name, String value, String indicator) { if (value != null) { // Get substring that is before the indicator, which is the name // of a type or of a particular field. String fieldOrType = name; int i = name.indexOf(DOT + indicator); if (i > 0) { fieldOrType = name.substring(0, i); } JDBCInfo ji = (JDBCInfo) jdbcInfoMap.get(fieldOrType); try { if (null != ji) { ji.setValue(value, indicator); } else { ji = new JDBCInfo(); ji.setValue(value, indicator); jdbcInfoMap.put(fieldOrType, ji); } } catch (JDBCInfo.IllegalJDBCTypeException ex) { String msg = I18NHelper.getMessage( messages, "EXC_InvalidJDBCTypeName", // NOI18N value, fieldOrType); logger.log(Logger.SEVERE, msg); throw new IllegalArgumentException(msg); } } } /** * Sets a name length attribute. * @param name Name of the attribute to set. Should be * INDICATOR_TABLE_NAME, INDICATOR_COLUMN_NAME, or * INDICATOR_CONSTRAINT_NAME. * @param value Value to which attribute is set. */ private void setLengthEntry(String name, String value) { if (value != null) { int val = Integer.parseInt(value); if (name.startsWith(INDICATOR_TABLE_NAME)) { tableNameMaxLength = val; } else if (name.startsWith(INDICATOR_COLUMN_NAME)) { columnNameMaxLength = val; } else if (name.startsWith(INDICATOR_CONSTRAINT_NAME)) { constraintNameMaxLength = val; } } } /** * Set a naming property in this MappingPolicy. * @param name Name of the policy property. * @param value Value to be bound to that property. */ private void setNamingEntry(String name, String value) { namingPolicy.put(name, value); } /** * Sets a JDBC-to-SQL mapping property, that is, a mapping from a JDBC * type to a SQL type. * @param name Name of a JDBC type (see {@link java.sql.Types}). * @param value SQL type to be used to represent given JDBC type in * database. */ private void setSQLInfoEntry(String name, String value) { sqlInfo.put(getJdbcType(name), value); } /** * @param databaseType Name of a type of database. * @return Name of a file containing a description of properties for * named database type. */ private static String getPropertyFileName(String databaseType) { return PROPERTY_FILE_DIR + databaseType + PROPERTY_FILE_EXT; } /** * Debug support. * @return A description of this MappingPolicy in string form. * Basically, all it's "interesting" values. */ public String toString() { StringBuilder rc = new StringBuilder( "statementSeparator=" + statementSeparator // NOI18N + "\ncreateTableStart=" + createTableStart // NOI18N + "\ncreateTableEnd=" + createTableEnd // NOI18N + "\ncreateIndex=" + createIndex // NOI18N + "\ndropTable=" + dropTable // NOI18N + "\nalterTableAddConstraintStart=" + alterTableAddConstraintStart // NOI18N + "\nalterTableDropConstraint=" + alterTableDropConstraint // NOI18N + "\nprimaryKeyConstraint=" + primaryKeyConstraint // NOI18N + "\nforeignKeyConstraint=" + foreignKeyConstraint // NOI18N + "\ncolumnNullability=" + columnNullability // NOI18N + "\nlobLogging=" + lobLogging // NOI18N + "\ntableNameMaxLength=" + tableNameMaxLength // NOI18N + "\ncolumnNameMaxLength=" + columnNameMaxLength // NOI18N + "\nconstraintNameMaxLength=" + constraintNameMaxLength // NOI18N + "\nuniqueTableName=" + uniqueTableName // NOI18N + "\ncounter=" + counter // NOI18N + "\n\n"); // NOI18N rc.append(" dbJdbcInfoMap:\n").append(stringifyMap(dbJdbcInfoMap)); // NOI18N rc.append(" userJdbcInfoMap:\n").append(stringifyMap(userJdbcInfoMap)); // NOI18N rc.append(" sqlInfo:\n").append(stringifyMap(sqlInfo)); // NOI18N rc.append(" namingPolicy:\n").append(stringifyMap(namingPolicy)); // NOI18N rc.append(" namespaces:\n").append(stringifyMap(namespaces)); // NOI18N rc.append(" reservedWords:\n").append(stringifySet(reservedWords)); // NOI18N return rc.toString(); } /** * Debug support. * @param m Map whose keys & values are to be returned in a string. * @return The string form of m's keys and values, each pair separated * from the next by a newline, with keys separated from values by '='. */ private String stringifyMap(Map m) { StringBuilder rc = new StringBuilder(); for (Iterator i = m.entrySet().iterator(); i.hasNext();) { Map.Entry e = (Map.Entry) i.next(); rc.append(e.getKey()).append("=") // NOI18N .append(e.getValue()).append("\n"); // NOI18N } return rc.toString(); } /** * Debug support * @param s Set whose values are to be returned in a string * @return values from the given set, separated by spaces, up to 6 per * line. */ private String stringifySet(Set s) { StringBuilder rc = new StringBuilder(); int count = 0; for (Iterator i = s.iterator(); i.hasNext();) { rc.append(i.next()).append(" "); // NOI18N if (count++ > 6) { rc.append("\n"); // NOI18N count = 0; } } return rc.toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy