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

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

// 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 - 2025 Weber Informatics LLC | Privacy Policy