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

gov.nasa.pds.tools.dict.Dictionary Maven / Gradle / Ivy

Go to download

The PDS3 Product Tools Library project supports design/generation, validation and submission of archival products to the PDS. This project consists of a library of software classes to support the development of tools to perform these functions and is designed to be utilized by developers from the Engineering Node, Discipline Nodes and the PDS community.

The newest version!
// Copyright 2019, California Institute of Technology ("Caltech").
// U.S. Government sponsorship acknowledged.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// • Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// • Redistributions must reproduce the above copyright notice, this list of
// conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
// • Neither the name of Caltech nor its operating division, the Jet Propulsion
// Laboratory, nor the names of its contributors may be used to endorse or
// promote products derived from this software without specific prior written
// permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package gov.nasa.pds.tools.dict;

import java.io.File;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import gov.nasa.pds.tools.LabelParserException;
import gov.nasa.pds.tools.constants.Constants.DictionaryType;
import gov.nasa.pds.tools.containers.SimpleDictionaryChange;
import gov.nasa.pds.tools.dict.parser.DictIDFactory;

/**
 * This class represents a PDS data dictionary.
 * 
 * @author pramirez
 * @author jagander
 * @version $Revision$
 * 
 */
public class Dictionary implements Serializable {
  private static final Logger log = LogManager.getLogger(Dictionary.class.getName());

  private static final long serialVersionUID = 1L;

  public static final Pattern VERSION_REGEX = Pattern.compile("Online Database: pdscat(.+) \\*"); //$NON-NLS-1$

  private Map definitions = new HashMap();

  private String information = ""; //$NON-NLS-1$

  private final URI dictionaryURI;

  private final File dictionaryFile;

  private final String sourceString;

  private Map units = new HashMap();

  private final List mergeChanges = new ArrayList();

  private final List problems = new ArrayList();

  public Dictionary() {
    this.dictionaryURI = null;
    this.dictionaryFile = null;
    this.sourceString = null;
  }

  public Dictionary(URI dictionaryURI) {
    this.dictionaryURI = dictionaryURI;
    this.sourceString = this.dictionaryURI.toString();
    this.dictionaryFile = null;
  }

  public Dictionary(final File dictionaryFile) {
    this.dictionaryFile = dictionaryFile;
    this.sourceString = this.dictionaryFile.toString();
    this.dictionaryURI = null;
  }

  public URI getDictionaryURI() {
    return this.dictionaryURI;
  }

  public File getDictionaryFile() {
    return this.dictionaryFile;
  }

  public String getSourceString() {
    return this.sourceString;
  }

  public String getVersion() {
    // TODO: have a better way of extracting version info than munged
    // comment parsing
    final String info = this.getInformation();
    final Matcher matcher = VERSION_REGEX.matcher(info);
    if (matcher.find()) {
      String rawVersion = matcher.group(1);
      // version looks like 1r77, switch to 1.77
      return rawVersion.replaceFirst("r", "."); //$NON-NLS-1$ //$NON-NLS-2$
    }
    return null;

  }

  /**
   * Merges the definitions in the dictionaries
   * 
   * @param dictionary to be merged into this dictionary. dictionary merged in will override values
   *        in this dictionary if scalars or be added if lists
   */

  public void merge(final Dictionary dictionary) {
    // iterate over passed in dictionary and put defs in local
    final Iterator> it =
        dictionary.definitions.entrySet().iterator();
    while (it.hasNext()) {
      final Entry entry = it.next();
      final DictIdentifier key = entry.getKey();
      final Definition overwrite = entry.getValue();
      // only try to do override if the def exists in the local dict
      if (this.definitions.containsKey(key)) {

        final Definition local = this.definitions.get(key);

        // first do stuff common to all types
        // merge aliases
        if (overwrite.hasAliases()) {
          final List newVals = overwrite.getAliases();
          final List oldVals = local.getAliases();

          // iterate over local
          for (final Alias alias : newVals) {
            // alias didn't already exist
            if (!oldVals.contains(alias)) {
              local.addAliasSimple(alias);
              // TODO: move out of loop and consolidate message?
              addMerge(overwrite, "dictionary.text.aliasAdded", //$NON-NLS-1$
                  alias);
            }
          }
        }

        // update description
        if (overwrite.hasDescription()) {
          final String oldVal = local.getDescription();
          final String newVal = overwrite.getDescription();
          if (!newVal.equals(oldVal)) {
            local.setDescription(newVal);
            addMerge(overwrite, "dictionary.text.descriptionChanged", newVal, //$NON-NLS-1$
                oldVal);
          }
        }

        // update status type
        // NOTE: not sure this should be overridden but... why not?
        // NOTE: could set to not approved since you're modding it but
        // not sure if worth it since falsly reports a value change you
        // didn't make
        if (overwrite.hasStatusType()) {
          final String oldVal = local.getStatusType();
          final String newVal = overwrite.getStatusType();
          if (!newVal.equals(oldVal)) {
            local.setStatusType(overwrite.getStatusType());
            addMerge(overwrite, "dictionary.text.statusTypeChanged", newVal, //$NON-NLS-1$
                oldVal);
          }
        }

        // TODO: skip and add problem if definition types disagree
        // update element definition type defs
        if (overwrite instanceof ElementDefinition) {
          ElementDefinition overwriteElement = (ElementDefinition) overwrite;
          ElementDefinition localElement = (ElementDefinition) local;

          // update data type
          if (overwriteElement.getDataType() != null) {
            final DictionaryType oldVal = localElement.getDataType();
            final DictionaryType newVal = overwriteElement.getDataType();
            if (!newVal.equals(oldVal)) {
              localElement.setDataType(newVal);
              addMerge(overwrite, "dictionary.text.dataTypeChanged", newVal, //$NON-NLS-1$
                  oldVal);
            }
          }

          // update max value (ie 100 is below max of 101)
          if (overwriteElement.hasMaximum()) {
            final Number oldVal = localElement.getMaximum();
            final Number newVal = overwriteElement.getMaximum();
            if (!newVal.equals(oldVal)) {
              localElement.setMaximum(newVal);
              addMerge(overwrite, "dictionary.text.maximumChanged", newVal, //$NON-NLS-1$
                  oldVal);
            }
          }

          // update max length of value (ie FOO has length of 3)
          if (overwriteElement.hasMaxLength()) {
            final Integer oldVal = localElement.getMaxLength();
            final Integer newVal = overwriteElement.getMaxLength();
            if (!newVal.equals(oldVal)) {
              localElement.setMaxLength(newVal);
              addMerge(overwrite, "dictionary.text.maxLengthChanged", newVal, //$NON-NLS-1$
                  oldVal);
            }
          }

          // update min value
          if (overwriteElement.hasMinimum()) {
            final Number oldVal = localElement.getMinimum();
            final Number newVal = overwriteElement.getMinimum();
            if (!newVal.equals(oldVal)) {
              localElement.setMinimum(newVal);
              addMerge(overwrite, "dictionary.text.minimumChanged", newVal, //$NON-NLS-1$
                  oldVal);
            }
          }

          // update min length
          if (overwriteElement.hasMinLength()) {
            final Integer oldVal = localElement.getMinLength();
            final Integer newVal = overwriteElement.getMinLength();
            if (!newVal.equals(oldVal)) {
              localElement.setMinLength(newVal);
              addMerge(overwrite, "dictionary.text.minLengthChanged", newVal, //$NON-NLS-1$
                  oldVal);
            }
          }

          // update unit type
          if (overwriteElement.getUnits() != null) {
            final String oldVal = localElement.getUnits();
            final String newVal = overwriteElement.getUnits();
            if (!newVal.equals(oldVal)) {
              localElement.setUnits(newVal);
              addMerge(overwrite, "dictionary.text.unitsChanged", //$NON-NLS-1$
                  newVal, oldVal);
            }
          }

          // merge values
          if (overwriteElement.hasValidValues()) {
            final Collection newVals = overwriteElement.getValues();
            final Collection oldVals = localElement.getValues();

            // iterate over local
            for (final String value : newVals) {
              // alias didn't already exist
              if (!oldVals.contains(value)) {
                localElement.addValue(value);
                // TODO: move out of loop and consolidate
                // message?
                addMerge(overwriteElement, "dictionary.text.valueAdded", //$NON-NLS-1$
                    value);
              }
            }
          }

          // update value type
          if (overwriteElement.getValueType() != null) {
            final String oldVal = localElement.getValueType();
            final String newVal = overwriteElement.getValueType();
            if (!newVal.equals(oldVal)) {
              localElement.setValueType(newVal);
              addMerge(overwrite, "dictionary.text.valueTypeChanged", //$NON-NLS-1$
                  newVal, oldVal);
            }

          }

        } else if (overwrite instanceof GroupDefinition || overwrite instanceof ObjectDefinition) {
          ContainerDefinition overwriteElement = (ContainerDefinition) overwrite;
          ContainerDefinition localElement = (ContainerDefinition) local;

          // merge option elements
          if (overwriteElement.hasOptionalElements()) {
            final List newVals = overwriteElement.getOptionalElements();
            final List oldVals = localElement.getOptionalElements();

            // iterate over local
            for (final DictIdentifier value : newVals) {
              // value didn't already exist
              if (!oldVals.contains(value)) {
                localElement.addOptional(value);
                // TODO: move out of loop and consolidate
                // message?
                addMerge(overwriteElement, "dictionary.text.optionalElementAdded", //$NON-NLS-1$
                    value);
              }
            }
          }

          // merge required elements
          if (overwriteElement.hasRequiredElements()) {
            final List newVals = overwriteElement.getRequiredElements();
            final List oldVals = localElement.getRequiredElements();

            // iterate over local
            for (final DictIdentifier value : newVals) {
              // value didn't already exist
              if (!oldVals.contains(value)) {
                localElement.addRequired(value);
                // TODO: move out of loop and consolidate
                // message?
                addMerge(overwriteElement, "dictionary.text.requiredElementAdded", //$NON-NLS-1$
                    value);
              }
            }
          }

        }
        if (overwrite instanceof ObjectDefinition) {
          ObjectDefinition overwriteElement = (ObjectDefinition) overwrite;
          ObjectDefinition localElement = (ObjectDefinition) local;

          // merge optional objects
          if (overwriteElement.hasOptionalObjects()) {
            final List newVals = overwriteElement.getOptionalObjects();
            final List oldVals = localElement.getOptionalObjects();

            // iterate over local
            for (final DictIdentifier value : newVals) {
              // value didn't already exist
              if (!oldVals.contains(value)) {
                localElement.addOptional(value);
                // TODO: move out of loop and consolidate
                // message?
                addMerge(overwriteElement, "dictionary.text.optionalObjectAdded", //$NON-NLS-1$
                    value);
              }
            }
          }

          // merge optional objects
          if (overwriteElement.hasRequiredObjects()) {
            final List newVals = overwriteElement.getRequiredObjects();
            final List oldVals = localElement.getRequiredObjects();

            // iterate over local
            for (final DictIdentifier value : newVals) {
              // value didn't already exist
              if (!oldVals.contains(value)) {
                localElement.addRequired(value);
                // TODO: move out of loop and consolidate
                // message?
                addMerge(overwriteElement, "dictionary.text.requiredObjectAdded", //$NON-NLS-1$
                    value);
              }
            }
          }

        }
      } else {
        this.definitions.put(key, overwrite);
        addMerge(overwrite, "dictionary.text.definitionAdded", //$NON-NLS-1$
            key);
      }
    }
    // Merge the units
    units.putAll(dictionary.getUnits());
  }

  /**
   * Tests to see whether or not a definition exists
   * 
   * @param identifier of the definition
   * @return flag indicating existence
   */
  public boolean containsDefinition(DictIdentifier identifier) {
    return this.definitions.containsKey(identifier);
  }

  /**
   * Tests to see whether or not an object is defined
   * 
   * @param identifier of the object
   * @return flag indicating existence
   */
  public boolean containsObjectDefinition(DictIdentifier identifier) {
    return containsDefinition(identifier);
  }

  /**
   * Tests to see whether or not a group is defined
   * 
   * @param identifier of the the group
   * @return flag indicating existence
   */
  public boolean containsGroupDefinition(DictIdentifier identifier) {
    return containsDefinition(identifier);
  }

  /**
   * Tests to see whether or not an element is defined
   * 
   * @param identifier of the element
   * @return flag indicating existence
   */
  public boolean containsElementDefinition(DictIdentifier identifier) {
    return containsElementDefinition(null, identifier);
  }

  public boolean containsElementDefinition(String objectContext, DictIdentifier identifier) {

    // try with context first
    if (objectContext != null) {
      DictIdentifier id =
          DictIDFactory.createElementDefId(objectContext + "." + identifier.toString()); //$NON-NLS-1$
      if (containsDefinition(id)) {
        return true;
      }
    }

    return containsDefinition(identifier);
  }

  /**
   * Retrieves the definition from the dictionary or null if not found
   * 
   * @param identifier of the definition
   * @return the definition
   */
  public Definition getDefinition(DictIdentifier identifier) {
    return this.definitions.get(identifier);
  }

  /**
   * Retrieves the object definition from the dictionary or null if not found
   * 
   * @param identifier of the definition
   * @return the object definition
   */
  public ObjectDefinition getObjectDefinition(DictIdentifier identifier) {
    Definition definition = this.definitions.get(identifier);
    if (definition != null && definition instanceof ObjectDefinition) {
      return (ObjectDefinition) definition;
    }
    return null;
  }

  /**
   * Retrieves the group definition from the dictionary or null if not found
   * 
   * @param identifier of the definition
   * @return the group definition
   */
  public GroupDefinition getGroupDefinition(DictIdentifier identifier) {
    Definition definition = this.definitions.get(identifier);
    if (definition != null && definition instanceof GroupDefinition) {
      return (GroupDefinition) definition;
    }
    return null;
  }

  /**
   * Retrieves the element definition from the dictionary or null if not found.
   * 
   * @param identifier of the definition
   * @return the element definition
   */
  public ElementDefinition getElementDefinition(DictIdentifier identifier) {
    return getElementDefinition(null, identifier);
  }

  public ElementDefinition getElementDefinition(String objectContext, DictIdentifier identifier) {
    Definition definition = null;

    // try with context first
    if (objectContext != null) {
      DictIdentifier id =
          DictIDFactory.createElementDefId(objectContext + "." + identifier.toString());//$NON-NLS-1$
      definition = this.definitions.get(id);
      if (definition != null) {
        return (ElementDefinition) definition;
      }
    }

    definition = this.definitions.get(identifier);
    if (definition != null) {
      return (ElementDefinition) definition;
    }

    return null;
  }

  /**
   * Adds a definition to this dictionary.
   * 
   * @param definition to be added to the dictionary
   */
  public void addDefinition(Definition definition) {
    final DictIdentifier id = definition.getIdentifier();
    if (!this.definitions.containsKey(id)) {
      this.definitions.put(id, definition);
      for (Iterator i = definition.getAliases().iterator(); i.hasNext();) {
        Alias alias = i.next();
        final DictIdentifier aliasId = new DictIdentifier(alias, definition.getClass());
        this.definitions.put(aliasId, definition);
      }
    } else {
      log.info(definition.getIdentifier() + " of type " //$NON-NLS-1$
          + definition.getClass() + " definition already exists"); //$NON-NLS-1$
    }
  }

  /**
   * Sets the description information for a dictionary. This is often captured informally in
   * comments at the top of a dictionary file.
   * 
   * @param information
   */
  public void setInformation(String information) {
    this.information = information;
  }

  /**
   * Return the dictionary's descriptive information.
   * 
   * @return the information
   */
  public String getInformation() {
    return this.information;
  }

  /**
   * Adds a list of definitions to this dictionary. The flag indicates whether the definitions
   * should be overwritten.
   * 
   * @param defs to be added to the dictionary
   */
  public void addDefinitions(Collection defs) {
    for (Iterator i = defs.iterator(); i.hasNext();) {
      addDefinition(i.next());
    }
  }

  /**
   * Retrieves the class definition for an object with the given identifier. This method will search
   * the dictionary for an ObjectDefinition whose identifier is the greatest length and matches the
   * end of the given identifier
   * 
   * @param identifier to lookup up class of
   * @return {@link ObjectDefinition} of class that will constrain object with given identifier.
   *         Returns null if not found.
   */
  public ObjectDefinition findObjectClassDefinition(DictIdentifier identifier) {
    ObjectDefinition definition = null;
    DictIdentifier curID = identifier;
    String className = identifier.toString();
    boolean done = false;

    while (definition == null && !done) {
      if (containsDefinition(curID)) {
        definition = (ObjectDefinition) this.definitions.get(curID);
      } else {
        if (className.indexOf("_") == -1 //$NON-NLS-1$
            || className.indexOf("_") == className.length() - 1) { //$NON-NLS-1$
          done = true;
        } else {
          className = className.substring(className.indexOf("_") + 1); //$NON-NLS-1$
          curID = DictIDFactory.createObjectDefId(className);
        }
      }
    }

    return definition;
  }

  /**
   * Retrieves the map of definitions
   * 
   * @return the map of definitions.
   */
  protected Map getDefinitions() {
    return this.definitions;
  }

  /**
   * Retrieves the class definition for a group with the given identifier. This method will search
   * the dictionary for a GroupDefinition whose identifier is the greatest length and matches the
   * end of the given identifier
   * 
   * @param identifier to lookup up class of
   * @return {@link GroupDefinition} of class that will constrain object with given identifier.
   *         Returns null if not found.
   */
  public GroupDefinition findGroupClassDefinition(DictIdentifier identifier) {
    GroupDefinition definition = null;
    DictIdentifier curID = identifier;
    String className = identifier.toString();
    boolean done = false;

    while (definition == null && !done) {
      if (containsGroupDefinition(curID)) {
        definition = (GroupDefinition) this.definitions.get(curID);
      } else {
        if (className.indexOf("_") == -1 || className.indexOf("_") == className.length() - 1) {//$NON-NLS-1$ //$NON-NLS-2$
          done = true;
        } else {
          className = className.substring(className.indexOf("_") + 1); //$NON-NLS-1$
          curID = DictIDFactory.createGroupDefId(className);
        }
      }
    }

    return definition;
  }

  public List getMergeChanges() {
    return this.mergeChanges;
  }

  public void addProblem(LabelParserException exception) {
    this.problems.add(exception);
  }

  public void addProblems(final List exceptions) {
    this.problems.addAll(exceptions);
  }

  public List getProblems() {
    return this.problems;
  }

  // use this for the master DD since it's not the user's responsibility to
  // correct errors there
  public void clearProblems() {
    this.problems.clear();
  }

  public Map getUnits() {
    return this.units;
  }

  public void setUnits(Map units) {
    this.units = units;
  }

  private void addMerge(final Definition definition, final String messageKey,
      final Object... arguments) {
    final SimpleDictionaryChange change =
        new SimpleDictionaryChange(definition, messageKey, arguments);
    this.mergeChanges.add(change);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy