Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * Copyright (C) 2014 Dell, Inc.
   * 
   * Licensed 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 com.dell.doradus.common;
 
 import java.util.Date;
 import java.util.Map;
 import java.util.Set;
Holds the definition of an object table.
 
 final public class TableDefinition {
     // Milliseconds within various units:
     private static final long MILLIS_IN_HOUR = 1000 * 60 * 60;      // 3,600,000
     private static final long MILLIS_IN_DAY =  * 24;  // 86,400,000
     private static final long MILLIS_IN_WEEK =  * 7;   // 604,800,000
     
     // Application to which we belong:
     private ApplicationDefinition m_appDef;
     
     // This table's logical (Doradus) application, unique to its owning application:
     private String m_tableName;
     
     // Map of options used by this table. Option names are stored down-cased.
     private final Map<StringStringm_optionMap =
         new HashMap<StringString>();
     
     // Sharding start date and granularity from parsed options:
     private GregorianCalendar   m_shardingStartDate;
     
     // Map of fields that this table owns. The map is final, but the field definitions
     // can be updated.
     private final SortedMap<StringFieldDefinitionm_fieldDefMap =
         new TreeMap<StringFieldDefinition>();
     
     // Map of alias definitions sorted by name:
     private final SortedMap<StringAliasDefinitionm_aliasDefMap =
         new TreeMap<StringAliasDefinition>();
     
     // The values we support for sharding-granularity:
     public enum ShardingGranularity {
         HOUR,
         DAY,
         WEEK,
         MONTH;
        
        
Return the TableDefinition.ShardingGranularity for the given granularity in string form. If the value is unrecognized, null is returned. This method can be used to validate granularity values.

Parameters:
value Sharding granularity as a string (case-insensitive): e.g., DAY, WEEK, or MONTH.
Returns:
TableDefinition.ShardingGranularity for given string or null if the value is unrecognized.
 
         public static ShardingGranularity fromString(String value) {
             // Since we only have a few values, just search.
             for (ShardingGranularity gran : values()) {
                 if (gran.toString().equalsIgnoreCase(value)) {
                     return gran;
                 }
             }
             return null;
         }   // fromString
         
     }   // enum ShardingGranularity
     
    
Create a new empty TableDefinition that belongs to the given application. It will not have a name or any fields until they parsed or added.

Parameters:
appDef Application to which table belongs.
 
     public TableDefinition(ApplicationDefinition appDef) {
         assert appDef != null;
          = appDef;
    }   // constructor

    
Create a new TableDefinition with the given name and belonging to the given application but with all default options and no initial fields. This constructor is used by implicitly-defined tables.

Parameters:
appDef Application to which table belongs.
tableName Unique table name.
    public TableDefinition(ApplicationDefinition appDefString tableName) {
        assert appDef != null;
        assert tableName != null && tableName.length() > 0;
        
        // Validate table name.
        if (!isValidTableName(tableName)) {
            throw new IllegalArgumentException("Invalid table name: " + tableName);
        }
        
        // Save parameters
         = appDef;
         = tableName;
    }   // constructor
    
    
Parse a table definition rooted at the given UNode. The given node is the table definition, hence its name is the table name. The node must be a MAP whose child nodes are table definitions such as "options" and "fields". This method is used when the table definition is not part of a larger application definition that may contain link forward-references.

Parameters:
tableNode UNode whose name is the table's name and whose child nodes define the tables features.
    public void parse(UNode tableNode) {
        parse(tableNodenull);
    }   // parse
    
    
Parse a table definition in rooted at the given UNode. The given node is the table definition, hence its name is the table name. The node must be a MAP whose child nodes are table definitions such as "options" and "fields".

Parameters:
tableNode UNode whose name is the table's name and whose child nodes define the tables features.
externalLinkMap Optional map of external link references. If this map is null, we do not attempt to generate or validate external link references.

If this map is not null, it may have values when we're called, and we may add it it. The map is

-to- <link name>-to-<field definition> where
appears to own the <link name>, which was defined as the 'inverse' of the link <field definition>. The caller must verify that each
and <link name> is valid and/or implicitly defined.
    public void parse(UNode                                     tableNode,
                      Map<StringMap<StringFieldDefinition>> externalLinkMap) {
        assert tableNode != null;
        assert  != null;
        assert .isEmpty();
        assert .isEmpty();
        assert  == null;
        
        // Node must be a map or a VALUE with a name only (empty table def).
        Utils.require(tableNode.isMap() || (tableNode.isValue() && Utils.isEmpty(tableNode.getValue())),
                      "'table' definition must be a map of unique names: " + tableNode);
        
        // Verify table name and save it.
        Utils.require(isValidTableName(tableNode.getName()),
                      "Invalid table name: " + tableNode.getName());
         = tableNode.getName();
        
        // Examine table node's children.
        for (String childName : tableNode.getMemberNames()) {
            // See if we recognize it.
            UNode childNode = tableNode.getMember(childName);
            
            // "fields"
            if (childName.equals("fields")) {
                // Value must be a map.
                Utils.require(childNode.isMap(),
                              "'fields' must be a map of unique names: " + childNode);
                
                // Process field definitions.
                for (String fieldName : childNode.getMemberNames()) {
                    // Create a FieldDefinition and parse the node's value into it.
                    // This will throw if the definition is invalid.
                    FieldDefinition fieldDef = new FieldDefinition(this);
                    fieldDef.parse(childNode.getMember(fieldName));
                    
                    // Ensure field name is unique and add it to the table's field map.
                    addFieldDefinition(fieldDef);
                    
                    // If the field is a group, verify that all nested field names are
                    // unique and add them to the field definition map.
                    if (fieldDef.isGroupField()) {
                        validateNestedFields(fieldDef);
                    }
                }
                
            // "options"
            } else if (childName.equals("options")) {
                // Value should be a map.
                Utils.require(childNode.isMap(),
                              "'options' must be a map of unique names: " + childNode);
                
                // Examine each option.
                for (String optName : childNode.getMemberNames()) {
                    // Each option must be a simple value and specified only once.
                    UNode optNode = childNode.getMember(optName);
                    Utils.require(optNode.isValue(),
                                  "'option' must be a value: " + optNode);
                    Utils.require(getOption(optName) == null,
                                  "Option '" + optName + "' can only be specified once");
                    
                    // Add option to option map, which performs some immediate validation.
                    setOption(optNameoptNode.getValue());
                }
                
            // "aliases"
            } else if (childName.equals("aliases")) {
                // Value should be a map.
                Utils.require(childNode.isMap(),
                              "'aliases' must be a map of unique names: " + childNode);
                
                // Parse and add each AliasDefinition.
                for (String aliasName : childNode.getMemberNames()) {
                    AliasDefinition aliasDef = new AliasDefinition();
                    aliasDef.parse(childNode.getMember(aliasName));
                    addAliasDefinition(aliasDef);
                }
            
            // Unrecognized
            } else {
                Utils.require(false"Unrecognized 'table' element: " + childName);
            }
        }
        
        // Finialize this table definition, including external link validation.
        finalizeTableDefinition(externalLinkMap);
    }   // parse

    
Indicate if the given string is a valid table name. Table names must begin with a letter and consist of all letters, digits, and underscores.

Parameters:
tableName Candidate table name.
Returns:
True if the name is not null, not empty, starts with a letter, and consists of only letters, digits, and underscores.
    public static boolean isValidTableName(String tableName) {
        return tableName != null &&
               tableName.length() > 0 &&
               Utils.isLetter(tableName.charAt(0)) &&
               Utils.allAlphaNumUnderscore(tableName);
    }   // isValidTableName
    ///// Getters

    
Compute and return the date at which the shard with the given number starts based on this table's sharding options. For example, if sharding-granularity is MONTH and the sharding-start is 2012-10-15, then shard 2 starts on 2012-11-01.

Parameters:
shardNumber Shard number (> 0).
Returns:
Date on which the given shard starts. It will >= this table's sharding-start option.
    public Date computeShardStart(int shardNumber) {
        assert isSharded();
        assert shardNumber > 0;
        assert  != null;
        
        // Shard #1 always starts on the sharding-start date.
        Date result = null;
        if (shardNumber == 1) {
            result = .getTime();
        } else {
            // Clone m_shardingStartDate and adjust by shard number.
            GregorianCalendar shardDate = (GregorianCalendar).clone();
            switch () {
            case :
                // Increment start date HOUR by shard number - 1.
                shardDate.add(.shardNumber - 1);
                break;
                
            case :
                // Increment start date DAY by shard number - 1.
                shardDate.add(.shardNumber - 1);
                break;
                
            case :
                // Round the sharding-start date down to the MONDAY of the same week.
                // Then increment it's DAY by (shard number - 1) * 7.
                shardDate = Utils.truncateToWeek();
                shardDate.add(., (shardNumber - 1) * 7);
                break;
            
            case :
                // Increment start date MONTH by shard number - 1, but the day is always 1.
                shardDate.add(.shardNumber - 1);
                shardDate.set(., 1);
                break;
            }
            result = shardDate.getTime();
        }
        assert computeShardNumber(result) == shardNumber;
        return result;
    }   // computeShardStart
    
    
Compute the shard number of an object belong to this table with the given sharding-field value. This method should only be called on a sharded table for which the sharding-field, sharding-granularity, and sharding-start options have been set. The value returned will be 0 if the given date falls before the sharding-start date. Otherwise it will be >= 1, representing the shard in which the object should reside.

Parameters:
shardingFieldValue Value of an object's sharding-field as a Date in the UTC time zone.
Returns:
Shard number representing the shard in which the object should reside.
    public int computeShardNumber(Date shardingFieldValue) {
        assert shardingFieldValue != null;
        assert isSharded();
        assert  != null;
        
        // Convert the sharding field value into a calendar object. Note that this value
        // will have non-zero time elements.
        GregorianCalendar objectDate = new GregorianCalendar(.);
        objectDate.setTime(shardingFieldValue);
        
        // Since the start date has no time elements, if the result is negative, the object's
        // date is before the start date. 
        if (objectDate.getTimeInMillis() < .getTimeInMillis()) {
            // Object date occurs before sharding start. Shard number -> 0.
            return 0;
        }
        // Determine the shard number based on the granularity.
        int shardNumber = 1;
        switch () {
        case :
            // Increment shard number by difference in millis-per-hours.
            shardNumber += (objectDate.getTimeInMillis() - .getTimeInMillis()) / ;
            break;
            
        case :
            // Since the dates are in UTC, which doesn't have DST, the difference in days
            // is simply the difference in whole number of millis-per-day.
            shardNumber += (objectDate.getTimeInMillis() - .getTimeInMillis()) / ;
            break;
            
        case :
            // Truncate the sharding-start date to MONDAY. The difference in weeks is then
            // the shard increment.
            GregorianCalendar shard1week = Utils.truncateToWeek();
            shardNumber += (objectDate.getTimeInMillis() - shard1week.getTimeInMillis()) / ;
            break;
            
        case :
            // Difference in months is 12 * (difference in years) + (difference in months)
            int diffInMonths = ((objectDate.get(.) - .get(.)) * 12) +
                               (objectDate.get(.) - .get(.));
            shardNumber += diffInMonths;
            break;
            
        default:
            Utils.require(false"Unknown sharding-granularity: " + );
        }
        return shardNumber;
    }   // computeShardNumber
    
    
Get the ApplicationDefinition to which this table definition applies. Note that while parsing table definitions, a TableDefinition object may not yet be known to the ApplicationDefinition to which it points.

Returns:
ApplicationDefinition to which this table definition applies.
    public ApplicationDefinition getAppDef() {
        return ;
    }   // getAppDef

    
Get the resource path for this table.

Returns:
String in the form {application path}/Table:{table}
    public String getPath() {
        return .getPath() + "/Table:" + ;
    }   // getPath
    
    
Get the name of the table represented by this table definition. This is the logical or "REST name", which is unique among tables owned by the application but not necessarily between applications. Also, not that the database table name will be different than this name.

Returns:
This table's name.
    public String getTableName() {
        return ;
    }   // getTableName
    
    
Get the field definitions for this table as an Collection<FieldDefinition> object. All outer and nested group, link, and scalar fields are returned. To expand group fields without traversing the same FieldDefinition twice, call this method and skip fields for which FieldDefinition.isNestedField() is true. Then, for each field for which FieldDefinition.isGroupField() is true, call FieldDefinition.getNestedFields() to iterate its immediate nested fields.

NOTE: The field definitions returned by this method's iterator are not copied, so be careful not to modify them!

Returns:
An iterator that returns all FieldDefinitions owned by this table.
        return .values();
    }   // getFieldDefinitions

    
Get the FieldDefinition for the field with the given name, used by this table. If there is no such field known, null is returned.

Parameters:
fieldName Candidate field name.
Returns:
FieldDefinition of corresponding field if known to this table, otherwise null.
    public FieldDefinition getFieldDef(String fieldName) {
        return .get(fieldName);
    }   // getFieldDef

    
Return the value of the option with the given name or null if there is no such option stored. This method does not verify that the option name is actually valid.

Parameters:
optionName Name of option (case-insensitive).
Returns:
Value of current value for option or null if there isn't one.
    public String getOption(String optionName) {
        return .get(optionName.toLowerCase());
    }   // getOption
    
    
Get a Set<String> of all option names currently defined for this table. For each option name in the returned set, getOption(java.lang.String) can be called to get the value of that option.

Returns:
Set of all option names currently defined for this table. The set will be empty if no options are defined.
    public Set<StringgetOptionNames() {
        return .keySet();
    }   // getOptionNames
    
    
Get the FieldDefinition of the sharding-field for this table. If this table is not sharded, the sharding-field option has not yet been set, or the sharding field has not yet been defined, null is returned.

Returns:
The FieldDefinition of the sharding-field for this table or null if the table is not sharded or the sharding-field has not yet been defined.
    public FieldDefinition getShardingField() {
        if (!isSharded()) {
            return null;
        }
        String shardingFieldNme = getOption(.);
        if (shardingFieldNme == null) {
            return null;
        }
        return getFieldDef(shardingFieldNme);   // may return null
    }   // getShardingField

    
Determine the shard number of the given object for this table. If this table is not sharded or the object has no value for its sharding field (see getShardingField()), the shard number is 0. Otherwise, the sharding-field value is used to determine the shard number based on the table's sharding start and granularity.

Note: The caller must ensure that if a value exists for the sharding-field, it is loaded into the given DBObject. Otherwise, this method will incorrectly assume the sharding value has not been set and therefore imply shard 0.

Parameters:
dbObj DBObject to determine shard number.
Returns:
0 if the object's owning table is not sharded, the object has no sharding-field value, or the object's sharding field value places it before sharding was started. Otherwise, the value is > 0.
    public int getShardNumber(DBObject dbObj) {
        if (!isSharded()) {
            return 0;
        }
        
        String value = dbObj.getFieldValue(getShardingField().getName());
        if (value == null) {
            return 0;
        }
        Date shardingFieldValue = Utils.dateFromString(value);
        return computeShardNumber(shardingFieldValue);
    }   // getShardNumber

    
Get the alias definitions owned by this table as an Iterable object. NOTE: The definitions are not copied, hence the caller must be careful not to modify it!

Returns:
AliasDefinitions owne by this table as an Iterable.
        return .values();
    }   // getAliasDefinitions
    
    
Get the AliasDefinition belonging to this table with the given name, or null if this table does not own such an alias.

Parameters:
aliasName Name of alias owned by this table.
Returns:
AliasDefinition of alias or null if unknown.
    public AliasDefinition getAliasDef(String aliasName) {
        return .get(aliasName);
    }   // getAliasDef
    
    
Return true if the given field name is an MV scalar field. Since scalar fields must be declared as MV, only predefined fields can be MV.

Parameters:
fieldName Candidate field name.
Returns:
True if the name is a known MV scalar field, otherwise false.
    public boolean isCollection(String fieldName) {
        FieldDefinition fieldDef = .get(fieldName);
        return fieldDef != null && fieldDef.isScalarField() && fieldDef.isCollection();
    }   // isCollection
    
    
Return true if this table definition possesses a FieldDefinition with the given name that defines a Link field.

Parameters:
fieldName Candidate field name.
Returns:
True if the name is a known Link field, otherwise false.
    public boolean isLinkField(String fieldName) {
        FieldDefinition fieldDef = .get(fieldName);
        return fieldDef != null && fieldDef.isLinkField();
    }   // isLinkField

    
Return true if this table definition possesses a FieldDefinition with the given name that defines a scalar field or if the field is undefined and therefore considered a text field.

Parameters:
fieldName Candidate field name.
Returns:
True if the name is a known scalar field, otherwise false.
    public boolean isScalarField(String fieldName) {
        FieldDefinition fieldDef = .get(fieldName);
        return fieldDef == null || fieldDef.isScalarField();
    }   // isScalarField
    
    
Return true if this table is sharded, meaning the sharding-field option has been defined. Note, however, that we don't check to see if the sharding field itself has been defined and is of the appropriate type.

Returns:
True if this table is sharded.
    public boolean isSharded() {
        return getOption(.) != null;
    }   // isSharded
    
    
Serialize this table definition into a UNode tree and return the root node.

Returns:
The root node of a UNode tree for this table definition.
    public UNode toDoc() {
        // The root node is a MAP, but we set its tag name to "table" for XML.
        UNode tableNode = UNode.createMapNode("table");
        
        // Add options, if any.
        if (getOptionNames().size() > 0) {
            // Options node is a MAP. 
            UNode optsNode = tableNode.addMapNode("options");
            for (String optName : getOptionNames()) {
                // Set the tag name of each option to "option" for XML.
                optsNode.addValueNode(optNamegetOption(optName), "option");
            }
        }
        
        // Add fields, if any.
        if (.size() > 0) {
            // Fields node is a MAP. 
            UNode fieldsNode = tableNode.addMapNode("fields");
            
            // Add definitions of outer (non-nested) fields only. Each group field will
            // recurse to its nested fields.
            for (FieldDefinition fieldDef : .values()) {
                if (!fieldDef.isNestedField()) {
                    fieldsNode.addChildNode(fieldDef.toDoc());
                }
            }
        }
        // Add "aliases", if any.
        if (.size() > 0) {
            // Aliases node is a MAP. 
            UNode aliasesNode = tableNode.addMapNode("aliases");
            for (AliasDefinition aliasDef : .values()) {
                aliasesNode.addChildNode(aliasDef.toDoc());
            }
        }
        
        return tableNode;
    }   // toDoc
    // Return "Table 'foo'"
    @Override
    public String toString() {
        return "Table '" +  + "'";
    }   // toString
    
    ///// Setters
    
    
Transfer this table definition to the given ApplicationDefinition by re-assigning its ApplicationDefinition pointer to the given one.

Parameters:
appDef New ApplicationDefinition owner of this table.
    public void setApplication(ApplicationDefinition appDef) {
        assert appDef != null;
         = appDef;
    }   // setApplication
    
    
Set this table's name to the given value. If the name is not a valid table name, an exception is thrown. This method should only be used when building a new TableDefinition -- it does not change the name of an existing table.

Parameters:
tableName New name of this table.
    public void setTableName(String tableName) {
        if (!isValidTableName(tableName)) {
            throw new IllegalArgumentException("Invalid table name: " + tableName);
        }
         = tableName;
    }   // setTableName
    
    
Set the option with the given name to the given value. This method does not validate that the given option name and value are valid since options are storage service-specific.

Parameters:
optionName Option name. This is down-cased when stored.
optionValue Option value. Cannot be null.
    public void setOption(String optionNameString optionValue) {
        // Ensure option value is not empty and trim excess whitespace.
        Utils.require(optionName != null"optionName");
        Utils.require(optionValue != null && optionValue.trim().length() > 0,
                      "Value for option '" + optionName + "' can not be empty");
        optionValue = optionValue.trim();
        .put(optionName.toLowerCase(), optionValue);
        
        // sharding-granularity and sharding-start are validated here since we must set
        // local members when the table's definition is parsed.
        if (optionName.equalsIgnoreCase(.)) {
             = ShardingGranularity.fromString(optionValue);
            Utils.require( != null,
                          "Unrecognized 'sharding-granularity' value: " + optionValue);
        } else if (optionName.equalsIgnoreCase(.)) {
            Utils.require(isValidShardDate(optionValue),
                          "'sharding-start' must be YYYY-MM-DD: " + optionValue);
             = new GregorianCalendar(.);
            .setTime(Utils.dateFromString(optionValue));
        }
    }   // setOption

    
Add the given FieldDefinition object to this table's list of known fields. The field should already be validated. If the field is a Link field, it is assigned the next available link ID for this table.

Parameters:
fieldDef New FieldDefinition to add to this table.
    public void addFieldDefinition(FieldDefinition fieldDef) {
        // Assure field name is unique and add the definition to the map.
        Utils.require(!.containsKey(fieldDef.getName()),
                      "Field names must be unique: " + fieldDef.getName());
        .put(fieldDef.getName(), fieldDef);
    }   // addFieldDefinition

    
Get the TableDefinition of the "extent" table for the given link field.

Parameters:
linkDef FieldDefinition for a link field.
Returns:
TableDefinition for link's extent (target) table.
        assert linkDef != null;
        assert linkDef.isLinkType();
        
        TableDefinition tableDef = .getTableDef(linkDef.getLinkExtent());
        assert tableDef != null;
        return tableDef;
    }   // getLinkExtentTableDef
    
    
Examine the given column name and, if it represents an MV link value, add it to the given MV link value map. If a link value is successfully extracted, true is returned. If the column name is not in the format used for MV link values, false is returned.

Parameters:
colName Column name from an object record belonging to this table (in string form).
mvLinkValueMap Link value map to be updated if the column represents a valid link value.
Returns:
True if a link value is extracted and added to the map; false means the column name was not a link value.
    public boolean extractLinkValue(String colNameMap<StringSet<String>> mvLinkValueMap) {
        // Link column names always begin with '~'.
        if (colName.length() == 0 || colName.charAt(0) != '~') {
            return false;
        }
        
        // A '/' should separate the field name and object value.
        int slashInx = colName.indexOf('/');
        if (slashInx < 0) {
            return false;
        }
        
        // Extract the field name and ensure we know about this field.
        String fieldName = colName.substring(1, slashInx);
        
        // Extract the link value's target object ID and add it to the value set for the field.
        String linkValue = colName.substring(slashInx + 1);
        Set<StringvalueSet = mvLinkValueMap.get(fieldName);
        if (valueSet == null) {
            // First value for this field.
            valueSet = new HashSet<String>();
            mvLinkValueMap.put(fieldNamevalueSet);
        }
        valueSet.add(linkValue);
        return true;
    }   // extractLinkValue
    
    ///// Private methods
    
    // Add the given alias definition to this table definition.
    private void addAliasDefinition(AliasDefinition aliasDef) {
        // Prerequisites:
        assert aliasDef != null;
        assert !.containsKey(aliasDef.getName());
        assert aliasDef.getTableName().equals(this.getTableName());
        
        .put(aliasDef.getName(), aliasDef);
    }   // addAliasDefinition
    // Perform final validation checks for the table definition we just pased.
    private void finalizeTableDefinition(Map<StringMap<StringFieldDefinition>> externalLinkMap) {
        // Options are validated by the assigned storage manager.
        
        // Validate implicit inverse links, if any.
        if (externalLinkMap != null) {
            // Examine all Link fields and make sure each 'inverse' is specified.
            validateInverseLinks(externalLinkMap);
        }
    }   // finalizeTableDefinition
    
    // Verify that the given shard-starting date is in the format YYYY-MM-DD. If the format
    // is bad, just return false.
    private boolean isValidShardDate(String shardDate) {
        try {
            // If the format is invalid, a ParseException is thrown.
            Utils.dateFromString(shardDate);
            return true;
        } catch (IllegalArgumentException ex) {
            return false;
        }
    }   // isValidShardDate
    // Examine all links defined in this table to ensure they are complete. If a link's
    // inverse is in this table but not explicitly defined, add it to the table's field
    // definitions. If a link's inverse is in another table, add it to the given
    // external link map. In the latter case, we also ensure that two different links are
    // not trying to declare the same link as their inverse.
    private void validateInverseLinks(Map<StringMap<StringFieldDefinition>> externalLinkMap) {
        // Take a snapshot of field names so we can add fields as iterate.
        Set<StringfieldNames = new HashSet<>(.keySet());
        for (String fieldName : fieldNames) {
            // Skip non-Link fields.
            FieldDefinition fieldDef = .get(fieldName);
            if (!fieldDef.isLinkType()) {
                continue;
            }
            
            // If the link is an xlink, validate the junction field.
            FieldType linkType = fieldDef.getType();
            if (linkType == .) {
                verifyJunctionField(fieldDef);
            }
            
            // See if link inverse is in this table or another.
            String linkInverse = fieldDef.getLinkInverse();
            assert linkInverse != null;
            String linkExtent = fieldDef.getLinkExtent();
            assert linkExtent != null;
            if (linkExtent.equals()) {
                // Inverse is in this table. See if the inverse field was explicitly defined.
                FieldDefinition inverseFieldDef = .get(linkInverse);
                if (inverseFieldDef == null) {
                    inverseFieldDef = new FieldDefinition(this);
                    inverseFieldDef.setType(linkType);
                    inverseFieldDef.setName(linkInverse);
                    inverseFieldDef.setLinkInverse(fieldName);
                    inverseFieldDef.setLinkExtent();
                    // by default, links are multi-valued
                    inverseFieldDef.setCollection(true);
                    addFieldDefinition(inverseFieldDef);
                } else {
                    // Inverse was explicitly defined. Ensure it points back to this link
                    // and this table.
                    Utils.require(inverseFieldDef.getLinkInverse().equals(fieldName),
                                  "Conflicting 'inverse' clauses for Link fields '" + fieldName +
                                  "' and '" + linkInverse + "'");
                    Utils.require(inverseFieldDef.getLinkExtent().equals(), 
                                  "Conflicting 'table' options for Link fields '" + fieldName +
                                  "' and '" + linkInverse + "'");
                }
            } else {
                // Another table is the target for this link. See if the other table is
                // already externally-referenced.
                Map<StringFieldDefinitionforwardLinks = externalLinkMap.get(linkExtent);
                if (forwardLinks == null) {
                    // First external reference to this table.
                    forwardLinks = new HashMap<StringFieldDefinition>();
                    externalLinkMap.put(linkExtentforwardLinks);
                }
                
                // See if the extent table already has a forward reference to this link.
                // If it does, this means two links both name the same link as their inverse!
                Utils.require(!forwardLinks.containsKey(linkInverse),
                              "Only one link field can define '" + linkInverse + "' in table '" +
                              linkExtent + "' as its 'inverse'");
                
                // Map the inverse link's name to us as its (eventual) inverse.
                forwardLinks.put(linkInversefieldDef);
            }
        }
    }   // validateInverseLinks
    
    // Verify that the given xlink's junction field is either _ID or an SV text field.
    private void verifyJunctionField(FieldDefinition xlinkDef) {
        String juncField = xlinkDef.getXLinkJunction();
        if (!"_ID".equals(juncField)) {
            FieldDefinition juncFieldDef = .get(juncField);
            Utils.require(juncFieldDef != null,
                            String.format("Junction field for xlink '%s' has not been defined: %s",
                                          xlinkDef.getName(), xlinkDef.getXLinkJunction()));
            //Utils.require(juncFieldDef.getType() == FieldType.TEXT && !juncFieldDef.isCollection(),
            //                String.format("Junction field for xlink '%s' must be an SV text field: ",
            //                              xlinkDef.getName(), xlinkDef.getXLinkJunction()));
            Utils.require(juncFieldDef.getType() == .,
                    String.format("Junction field for xlink '%s' must be a text field: ",
                                  xlinkDef.getName(), xlinkDef.getXLinkJunction()));
        }
    }   // verifyJunctionField
    // Verify that this group field's nested fields are uniquely named among all fields in
    // this table, and add each nested field to the global field map. This allows us to
    // treat nested fields individually as well as qualified via their group fields.
    private void validateNestedFields(FieldDefinition groupFieldDef) {
        assert groupFieldDef.isGroupField();
        
        for (FieldDefinition nestedFieldDef : groupFieldDef.getNestedFields()) {
            // addFieldDefinition() ensures that the field name is unique, adds the nested
            // field to the field map and, if it is a link, adds it to the link map if the
            // link has a field number.
            addFieldDefinition(nestedFieldDef);
            // If this nested field is a group, recurse to it.
            if (nestedFieldDef.isGroupField()) {
                validateNestedFields(nestedFieldDef);
            }
        }
    }   // validateNestedFields

    
Replaces of all occurences of aliases defined with this table, by their expressions. Now a simple string.replace is used.

Parameters:
str string to replace
Returns:
string with replaced aliases. If there were no aliases, the string is unchanged.
	public String replaceAliaces(String str) {
		return getAppDef().replaceAliaces(str);
	}
}   // class TableDefinition
New to GrepCode? Check out our FAQ X