EntityName.java

/*
** Module   : EntityName.java
** Abstract : Normalized representation of a schema entity name
**
** Copyright (c) 2004-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20041217   @19166 Created initial version.
** 002 ECF 20050118   @19340 Added methods to safely expose name component type constants.
** 003 ECF 20050411   @20704 Removed unused toNameNode method.
** 004 ECF 20150715          Replace StringBuffer with StringBuilder.
** 005 OM  20320111          Small optimization. Code maintenance.
** 006 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.schema;

import com.goldencode.p2j.convert.*;

import java.util.*;
import java.util.logging.*;

/**
 * Representation of a qualified Progress schema entity name, reflecting the
 * qualified name hierarchy represented by the Progress language naming
 * conventions for schema entity references. Possible permutations are:
 * <pre>
 *    database.table.field
 *    database.table
 *    table.field
 *    database
 *    table
 *    field
 * </pre>
 * where each part of a fully qualified name represents an entity which has
 * a one-to-many relationship with the entity to its right (if any), and a
 * one-to-one relationship with the entity to its left (if any).
 * <p>
 * The primary purpose of this class is to normalize entity names which may
 * be fully qualified, partially qualified, or unqualified into an object
 * which contains all parts of the entity name (even if this requires that
 * some remain null).
 */
public final class EntityName
{
   /** Logger. */
   private static final ConversionStatus LOG = ConversionStatus.get(EntityName.class);
   
   /** Constant indicating name represents a database */
   public static final int DATABASE = 0;
   
   /** Constant indicating name represents a table */
   public static final int TABLE = 1;
   
   /** Constant indicating name represents a field */
   public static final int FIELD = 2;
   
   /** Array of database, table, and field parts of qualified entity name */
   private final String[] names = new String[3];
   
   /**
    * Get the descriptive name of the specified entity type.
    *
    * @param   type
    *          Constant indicating type: database, table, or field.
    *
    * @return  Descriptive name of the type.
    */
   public static String getTypeName(int type)
   throws IllegalArgumentException
   {
      validateType(type);
      
      final String[] typeNames =
      {
         "database",
         "table",
         "field",
      };
      
      return typeNames[type];
   }
   
   /**
    * Establish whether <code>type</code> (presumably the type of an entity
    * name qualifier) has an associated "child" type, as described by the
    * {@link #getChildType} method description.
    *
    * @param   type
    *          Type of the qualifier using the type constants of this class.
    *
    * @return  <code>true</code> if <code>type</code> is either
    *          <code>DATABASE</code> or <code>TABLE</code>.
    *
    * @throws  IllegalArgumentException
    *          if <code>type</code> is neither <code>DATABASE</code> nor
    *          <code>TABLE</code>.
    *
    * @see     #getChildType
    * @see     #DATABASE
    * @see     #TABLE
    * @see     #FIELD
    */
   public static boolean hasChildType(int type)
   {
      return (type >= DATABASE && type < FIELD);
   }
   
   /**
    * Get the type (i.e., <code>TABLE</code> or <code>FIELD</code>) of the
    * next part of the entity name (as read from left to right), given the
    * type of a qualifier.
    *
    * @param   type
    *          Type of the qualifier; must be <code>DATABASE</code> or
    *          <code>TABLE</code>.
    *
    * @return  Type of the "child" of the qualifier type represented by the
    *          <code>type</code> argument. Will be <code>TABLE</code> if
    *          <code>type</code> is <code>DATABASE</code>; will be
    *          <code>FIELD</code> if <code>type</code> is <code>TABLE</code>.
    *
    * @throws  IllegalArgumentException
    *          if <code>type</code> is neither <code>DATABASE</code> nor
    *          <code>TABLE</code>.
    *
    * @see     #hasChildType
    * @see     #DATABASE
    * @see     #TABLE
    * @see     #FIELD
    */
   public static int getChildType(int type)
   throws IllegalArgumentException
   {
      if (!hasChildType(type))
      {
         throw new IllegalArgumentException("Invalid parent entity name type: " + type);
      }
      
      return (type + 1);
   }
   
   /**
    * Validate that <code>type</code> represents a valid entity name component
    * type (i.e., is one of the type constants defined by this class).
    *
    * @param   type
    *          Type constant to test for validity.
    *
    * @throws  IllegalArgumentException
    *          if <code>type</code> is not one of the defined type constants.
    *
    * @see     #getChildType
    * @see     #DATABASE
    * @see     #TABLE
    * @see     #FIELD
    */
   private static void validateType(int type)
   throws IllegalArgumentException
   {
      if (type < DATABASE || type > FIELD)
      {
         throw new IllegalArgumentException("Invalid entity name type: " + type);
      }
   }
   
   /**
    * Constructor which parses a qualified entity name into its constituent
    * parts.
    *
    * @param   type
    *          Constant indicating whether this object represents a database,
    *          table, or field.
    * @param   name
    *          Qualified or unqualified entity name to be parsed.
    *
    * @throws  IllegalArgumentException
    *          if <code>type</code> is not a valid type constant or if
    *          <code>name</code> does not conform to the accepted schema
    *          entity {@link EntityName naming conventions}.
    */
   public EntityName(int type, String name)
   throws IllegalArgumentException
   {
      parseName(type, name);
   }
   
   /**
    * Get the name for the specified part of the entity name.
    *
    * @param   type
    *          Constant indicating whether to return the database, table, or
    *          field portion of the name.
    *
    * @return  The name part corresponding with <code>type</code>. May be
    *          <code>null</code> if this object does not contain the specified
    *          part.
    */
   public String getPart(int type)
   {
      return names[type];
   }
   
   /**
    * Get the database qualifier part of the entity name.
    *
    * @return  The database qualifier part of the entity name. May be
    *          <code>null</code> if this entity name has no database
    *          qualifier.
    */
   public String getDatabase()
   {
      return names[DATABASE];
   }
   
   /**
    * Get the table qualifier part of the entity name.
    *
    * @return  The table qualifier part of the entity name. May be
    *          <code>null</code> if this entity name has no table
    *          qualifier, or if it does not represent a table.
    */
   public String getTable()
   {
      return names[TABLE];
   }
   
   /**
    * Get the field part of the entity name.
    *
    * @return  The field part of the entity name. May be <code>null</code> if
    *          this entity does not represent a field.
    */
   public String getField()
   {
      return names[FIELD];
   }
   
   /**
    * Creates a string representation of a normalized entity name. Some parts
    * of the name may be null. This is used primarily for debug purposes.
    *
    * @return  String representation of this object's internal state.
    */
   public String toString()
   {
      return names[DATABASE] + '.' + names[TABLE] + '.' + names[FIELD];
   }
   
   /**
    * Parse a qualified entity name into its constituent parts. This method
    * normalizes all entity names -- whether fully qualified, partially
    * qualified, or unqualified -- into a common format.
    *
    * @param   type
    *          Constant indicating whether this object represents a database,
    *          table, or field.
    * @param   name
    *          Qualified or unqualified entity name to be parsed.
    *
    * @throws  IllegalArgumentException
    *          if <code>type</code> is not a valid type constant or if
    *          <code>name</code> does not conform to the accepted schema
    *          entity {@link EntityName naming conventions}.
    */
   private void parseName(int type, String name)
   throws IllegalArgumentException
   {
      validateType(type);
      
      List<String> parts = new ArrayList<>(3);
      StringTokenizer tok = new StringTokenizer(name, ".");
      while (tok.hasMoreTokens())
      {
         parts.add(tok.nextToken());
      }
      
      int size = parts.size();
      if (size == 0 || size > type + 1)
      {
         throw new IllegalArgumentException("Invalid " + getTypeName(type) + " name: " + name);
      }
      
      for (int i = type; size > 0; i--)
      {
         size --;
         names[i] = parts.get(size);
      }
   }
   
   /**
    * Unit test driver for this class. Accepts an entity type and fully
    * qualified, partially qualified, or unqualified entity name and prints
    * the normalized form of the name.
    *
    * @param   args
    *          Command line arguments:
    *          <ol>
    *          <li>Entity type (must use integer type constants)</li>
    *          <li>Entity name string to be parsed</li>
    *          </ol>
    */
   public static void main(String[] args)
   {
      try
      {
         int type = Integer.parseInt(args[0]);
         System.out.println((new EntityName(type, args[1])));
      }
      catch (Exception exc)
      {
         LOG.log(Level.SEVERE, "", exc);
      }
   }
}