feature[F20620]: Implement CAT Plug-In Project Nature

Implement a project nature class to configure and deconfigure projects for the CAT annotation processor.

Change-Id: Icc45615604459b5524580ea73a2b416442a9ac05
diff --git a/org.eclipse.ote.cat.plugin/META-INF/MANIFEST.MF b/org.eclipse.ote.cat.plugin/META-INF/MANIFEST.MF
index 72a7fea..c070b0a 100644
--- a/org.eclipse.ote.cat.plugin/META-INF/MANIFEST.MF
+++ b/org.eclipse.ote.cat.plugin/META-INF/MANIFEST.MF
@@ -4,8 +4,8 @@
 Bundle-SymbolicName: org.eclipse.ote.cat.plugin;singleton:=true
 Bundle-Version: 0.26.3.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Bundle-ActivationPolicy: lazy
 Bundle-Activator: org.eclipse.ote.cat.plugin.CatPlugin
+Bundle-ActivationPolicy: lazy
 Automatic-Module-Name: org.eclipse.ote.cat.plugin
 Import-Package: 
  com.fasterxml.jackson.annotation,
@@ -13,18 +13,25 @@
  com.fasterxml.jackson.databind,
  org.eclipse.core.resources,
  org.eclipse.core.runtime,
+ org.eclipse.core.runtime.jobs,
  org.eclipse.core.runtime.preferences,
  org.eclipse.e4.core.services.log,
+ org.eclipse.jdt.core,
  org.eclipse.jface.dialogs,
  org.eclipse.jface.preference,
  org.eclipse.jface.viewers,
  org.eclipse.jface.window,
+ org.eclipse.osee.framework.core.util,
  org.eclipse.osee.framework.jdk.core.type,
+ org.eclipse.osee.framework.jdk.core.util,
  org.eclipse.swt,
  org.eclipse.swt.events,
  org.eclipse.swt.graphics,
  org.eclipse.swt.layout,
  org.eclipse.swt.widgets,
- org.osgi.framework
+ org.osgi.framework,
+ org.osgi.service.prefs
 Require-Bundle: 
- org.eclipse.ui
+ org.eclipse.ui,
+ org.eclipse.ui.ide,
+ org.eclipse.jdt.apt.core
diff --git a/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/cat-jar-file-error.html b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/cat-jar-file-error.html
new file mode 100644
index 0000000..b07c299
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/cat-jar-file-error.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<!--
+  == Copyright (c) 2024 Boeing
+  == 
+  == This program and the accompanying materials are made available under the
+  == terms of the Eclipse Public License 2.0 which is available at
+  == https://www.eclipse.org/legal/epl-2.0/
+  == 
+  == SPDX-License-Identifier: EPL-2.0
+  == 
+  == Contributors: Boeing - initial API and implementation
+-->
+<html>
+   <body>
+      <h1>CAT Jar File Error</h1>
+   </body>
+</html>
+
diff --git a/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/cat-plugin-state-file-error.html b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/cat-plugin-state-file-error.html
new file mode 100644
index 0000000..44f93d6
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/cat-plugin-state-file-error.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+  == Copyright (c) 2024 Boeing
+  == 
+  == This program and the accompanying materials are made available under the
+  == terms of the Eclipse Public License 2.0 which is available at
+  == https://www.eclipse.org/legal/epl-2.0/
+  == 
+  == SPDX-License-Identifier: EPL-2.0
+  == 
+  == Contributors: Boeing - initial API and implementation
+-->
+<html>
+   <body>
+      <h1>CAT Plug-In State File Error</h1>
+   </body>
+</html>
diff --git a/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/cat-project-info-file-error.html b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/cat-project-info-file-error.html
new file mode 100644
index 0000000..2c2ddc0
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/cat-project-info-file-error.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+  == Copyright (c) 2024 Boeing
+  == 
+  == This program and the accompanying materials are made available under the
+  == terms of the Eclipse Public License 2.0 which is available at
+  == https://www.eclipse.org/legal/epl-2.0/
+  == 
+  == SPDX-License-Identifier: EPL-2.0
+  == 
+  == Contributors: Boeing - initial API and implementation
+-->
+<html>
+   <body>
+      <h1>CAT Project Info File Error</h1>
+   </body>
+</html>
diff --git a/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/command-line-option-error.html b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/command-line-option-error.html
new file mode 100644
index 0000000..f0426dc
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/command-line-option-error.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+  == Copyright (c) 2024 Boeing
+  == 
+  == This program and the accompanying materials are made available under the
+  == terms of the Eclipse Public License 2.0 which is available at
+  == https://www.eclipse.org/legal/epl-2.0/
+  == 
+  == SPDX-License-Identifier: EPL-2.0
+  == 
+  == Contributors: Boeing - initial API and implementation
+-->
+<html>
+   <body>
+      <h1>Command Line Option Error</h1>
+   </body>
+</html>
diff --git a/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/default-preference-file-error.html b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/default-preference-file-error.html
new file mode 100644
index 0000000..ace382a
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/default-preference-file-error.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+  == Copyright (c) 2024 Boeing
+  == 
+  == This program and the accompanying materials are made available under the
+  == terms of the Eclipse Public License 2.0 which is available at
+  == https://www.eclipse.org/legal/epl-2.0/
+  == 
+  == SPDX-License-Identifier: EPL-2.0
+  == 
+  == Contributors: Boeing - initial API and implementation
+-->
+<html>
+   <body>
+      <h1>Default Preference File Error</h1>
+   </body>
+</html>
diff --git a/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/no-default-preferences-warning.html b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/no-default-preferences-warning.html
new file mode 100644
index 0000000..0e9473c
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/no-default-preferences-warning.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+  == Copyright (c) 2024 Boeing
+  == 
+  == This program and the accompanying materials are made available under the
+  == terms of the Eclipse Public License 2.0 which is available at
+  == https://www.eclipse.org/legal/epl-2.0/
+  == 
+  == SPDX-License-Identifier: EPL-2.0
+  == 
+  == Contributors: Boeing - initial API and implementation
+-->
+<html>
+   <body>
+      <h1>No Default Preferences Warning</h1>
+   </body>
+</html>
diff --git a/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/ple-configuration-cache-folder-error.html b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/ple-configuration-cache-folder-error.html
new file mode 100644
index 0000000..ba9dfa2
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/ple-configuration-cache-folder-error.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<!--
+  == Copyright (c) 2024 Boeing
+  == 
+  == This program and the accompanying materials are made available under the
+  == terms of the Eclipse Public License 2.0 which is available at
+  == https://www.eclipse.org/legal/epl-2.0/
+  == 
+  == SPDX-License-Identifier: EPL-2.0
+  == 
+  == Contributors: Boeing - initial API and implementation
+-->
+<html>
+   <body>
+      <h1>PLE Configuration Cache Folder Error</h1>
+   </body>
+</html>
+
diff --git a/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/preference-file-save-error.html b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/preference-file-save-error.html
new file mode 100644
index 0000000..00efc25
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/preference-file-save-error.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+  == Copyright (c) 2024 Boeing
+  == 
+  == This program and the accompanying materials are made available under the
+  == terms of the Eclipse Public License 2.0 which is available at
+  == https://www.eclipse.org/legal/epl-2.0/
+  == 
+  == SPDX-License-Identifier: EPL-2.0
+  == 
+  == Contributors: Boeing - initial API and implementation
+-->
+<html>
+   <body>
+      <h1>Preference File Save Error</h1>
+   </body>
+</html>
diff --git a/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/source-location-method-error.html b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/source-location-method-error.html
new file mode 100644
index 0000000..8a2d177
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/OSEE-INF/user-support/source-location-method-error.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+  == Copyright (c) 2024 Boeing
+  == 
+  == This program and the accompanying materials are made available under the
+  == terms of the Eclipse Public License 2.0 which is available at
+  == https://www.eclipse.org/legal/epl-2.0/
+  == 
+  == SPDX-License-Identifier: EPL-2.0
+  == 
+  == Contributors: Boeing - initial API and implementation
+-->
+<html>
+   <body>
+      <h1>Source Location Method Error</h1>
+   </body>
+</html>
diff --git a/org.eclipse.ote.cat.plugin/icons/catprojectnature.png b/org.eclipse.ote.cat.plugin/icons/catprojectnature.png
new file mode 100644
index 0000000..ebb7753
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/icons/catprojectnature.png
Binary files differ
diff --git a/org.eclipse.ote.cat.plugin/plugin.xml b/org.eclipse.ote.cat.plugin/plugin.xml
index cbbd7a9..a2eb6b7 100644
--- a/org.eclipse.ote.cat.plugin/plugin.xml
+++ b/org.eclipse.ote.cat.plugin/plugin.xml
@@ -15,7 +15,21 @@
   -->
 
 <plugin>
-
+   
+   <!--
+      == When the workbench is ready the "earlyStartup" method of the CAT Plug-In "Startup" class
+      == will be invoked. This will cause the CAT Plug-In bundle to be activated and begin the
+      == "CatPluginManager" cache load and synchronization.
+   -->
+     
+   <extension
+      point = "org.eclipse.ui.startup">
+   
+      <startup 
+         class = "org.eclipse.ote.cat.plugin.Startup" />
+         	
+   </extension>
+   
    <extension
       point = "org.eclipse.ui.preferencePages">
       
@@ -30,29 +44,29 @@
       
       <page
          class    = "org.eclipse.ote.cat.plugin.preferencepage.CatSettingsPreferencePage"
-         id       = "org.eclipse.ote.cat.plugin.preferencepage.cat"
+         id       = "preferencepage.cat"
          name     = "CAT" />
 
       <page
-         category = "org.eclipse.ote.cat.plugin.preferencepage.cat"
+         category = "preferencepage.cat"
          class    = "org.eclipse.ote.cat.plugin.preferencepage.PleConfigurationCachePreferencePage"
-         id       = "org.eclipse.ote.cat.plugin.preferencepages.pleconfigurationcache"
-         name     = "PLE Configuration Cache" />
+         id       = "preferencepages.pleconfigurationcache"
+         name     = "PLE Configuration Loader" />
       
    </extension>
    
    <!--
-      == The "id" attribute is expected to be set to:
-      ==
-      ==    <bundle-symbolic-name> ".defaultpreferenceinitializer"
-      ==
-      ==    where bundle-symbolic-name is the value for "Bundle-SymbolicName" set in the file
-      ==    "MANIFEST.MF". 
+      == This extension specification is found by the CAT Plug-In using the extension point identifier
+      == and the bundle symbolic name. The CAT Plug-In is expected to provide only on extension for the
+      == "org.eclipse.core.runtime.preferences" extention point. The configuration element ("initializer")
+      == and attribute ("option") that contains the default preferences command line option name are found
+      == by element and attribute names. These names must align with thoes defined in the
+      == org.eclipse.ote.cat.plugin.Constants class. 
    -->
    
    <extension
          point = "org.eclipse.core.runtime.preferences"
-         id    = "org.eclipse.ote.cat.plugin.defaultpreferenceinitializer">
+         id    = "defaultpreferenceinitializer">
          
       <!--
          == Set the value of the "option" attribute to be the command line option used to
@@ -67,4 +81,36 @@
       
    </extension>
    
+   <!--
+      == The CAT Plug-In project nature extension identifier is found using the extension point identifier
+      == and the bundle symbolic name. The CAT Plug-In is expected to provide only one extension for the
+      == "org.eclipse.core.resources.natures" extension point.
+   -->
+   
+   <extension
+      point = "org.eclipse.core.resources.natures"
+      id    = "catprojectnature">
+      
+      <runtime>
+      
+         <run
+            class = "org.eclipse.ote.cat.plugin.project.CatNature" />
+            
+      </runtime>
+      
+      <requires-nature
+         id = "org.eclipse.jdt.core.javanature" />
+          
+   </extension>
+   
+   <extension
+         point="org.eclipse.ui.ide.projectNatureImages">
+         
+      <image
+            icon="icons/catprojectnature.png"
+            id="catprojectnature.image"
+            natureId="org.eclipse.ote.cat.plugin.catprojectnature"/>
+            
+   </extension>
+    
 </plugin>
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/CatPlugin.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/CatPlugin.java
index 07ad5f7..4c30a98 100644
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/CatPlugin.java
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/CatPlugin.java
@@ -12,13 +12,22 @@
 
 package org.eclipse.ote.cat.plugin;
 
+import java.io.File;
 import java.util.Objects;
-import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtension;
+import org.eclipse.core.runtime.preferences.InstanceScope;
 import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.util.Policy;
+import org.eclipse.ote.cat.plugin.exception.CatErrorCode;
+import org.eclipse.ote.cat.plugin.exception.CatErrorSupportProvider;
+import org.eclipse.ote.cat.plugin.exception.CatPluginException;
+import org.eclipse.ote.cat.plugin.project.CatProjectManager;
+import org.eclipse.ote.cat.plugin.util.Extensions;
 import org.eclipse.ui.plugin.AbstractUIPlugin;
-import org.eclipse.ui.statushandlers.StatusManager;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.service.prefs.BackingStoreException;
 
 /**
  * An extension of the {@link AbstractUIPlugin} for the CAT Plug-in. This plug-in provides the ability to configure the
@@ -42,6 +51,111 @@
    private static CatPlugin instance = null;
 
    /**
+    * Finds the identifier of the CAT Plug-In extension for the extension point
+    * {@value Constants#naturesExtensionPointIdentifier} from the &quot;plugin.xml&quot; manifest file.
+    * 
+    * @return the full extension identifier with name space.
+    * @throws CatPluginException when an extension is not found or more than one extension is found for the extension
+    * point.
+    */
+
+   private static String findCatNatureIdentifier() {
+
+      try {
+         IExtension extension =
+            Extensions.getExtensions(Constants.naturesExtensionPointIdentifier, CatPlugin.getIdentifier(), 1).get(0);
+
+         String natureIdentifier = extension.getUniqueIdentifier();
+
+         return natureIdentifier;
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException findCatNatureIdentifierException =
+            new CatPluginException
+                   (
+                      CatErrorCode.InternalError,
+                      "Failed to get the \"CatNature\" identifier from the \"plugin.xml\" manifest."
+                   );
+         //@formatter:on
+         throw findCatNatureIdentifierException;
+      }
+   }
+
+   /**
+    * Finds the command line option name for the default preferences file in the &quot;plugin.xml&quot; file. The
+    * command line option is specified with the {@value Constants#preferencesCommandLineOptionAttributeName} attribute
+    * of the configuration element {@value Constants#preferencesInitializerConfigurationElement}; of the extension for
+    * the extension point {@value Constants#preferencesExtensionPointIdentifier}.
+    * 
+    * @return the command line option extension name for the default preferences file.
+    * @throws CatPluginException when an extension is not found or more than one extension is found for the extension
+    * point; the &quot;initializer&quot; configuration element is not found; or the &quot;option&quot; attribute is not
+    * found.
+    */
+
+   private static String findDefaultPreferencesCommandLineOptionName() {
+
+      try {
+         IExtension extension =
+            Extensions.getExtensions(Constants.preferencesExtensionPointIdentifier, CatPlugin.getIdentifier(), 1).get(
+               0);
+
+         IConfigurationElement configurationElement =
+            Extensions.getConfigurationElements(extension, Constants.preferencesInitializerConfigurationElement, 1).get(
+               0);
+
+         String option =
+            Extensions.getAttribute(configurationElement, Constants.preferencesCommandLineOptionAttributeName);
+
+         return option;
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException findDefaultPreferencesCommandLineOptionNameException =
+            new CatPluginException
+                   (
+                      CatErrorCode.InternalError,
+                      "Failed to get the command line option name for the default preferences file from the \"plugin.xml\" manifest."
+                   );
+         //@formatter:on
+         throw findDefaultPreferencesCommandLineOptionNameException;
+      }
+   }
+
+   /**
+    * Gets the extension identifier of the CAT Plug-In project nature.
+    * 
+    * @return the CAT Plug-In project nature identifier.
+    */
+
+   public static String getCatNatureIdentifier() {
+      assert Objects.nonNull(CatPlugin.instance) : "CatPlugin instance is unexpectedly null.";
+      return CatPlugin.instance.catNatureIdentifier;
+   }
+
+   /**
+    * Gets the {@link CatProjectManager} instance. The instance is created when the plug-in is started and
+    * <code>null</code>ed when the plug-in is stopped.
+    * 
+    * @return the {@link CatProjectManager} instance.
+    */
+
+   public static CatProjectManager getCatProjectManager() {
+      assert Objects.nonNull(CatPlugin.instance) : "CatPlugin instance is unexpectedly null.";
+      return CatPlugin.instance.catProjectManager;
+   }
+
+   /**
+    * Gets the command line option name for the default preferences file.
+    * 
+    * @return the command line option name.
+    */
+
+   public static String getDefaultPreferencesCommandLineOptionName() {
+      assert Objects.nonNull(CatPlugin.instance) : "CatPlugin instance is unexpectedly null.";
+      return CatPlugin.instance.defaultPreferencesCommandLineOptionName;
+   }
+
+   /**
     * Gets the {@link CatPlugin} OSGi bundle symbolic name as the identifier.
     * 
     * @return an identification string for the {@link CatPlugin}.
@@ -53,17 +167,6 @@
    }
 
    /**
-    * Gets the expected OSGi extension identifier for the extension that implements the
-    * "org.eclipse.core.runtime.preferences" extension point of the plug-in.
-    * 
-    * @return the extension identifier.
-    */
-
-   public static String getDefaultPreferenceInitializerExtensionIdentifier() {
-      return CatPlugin.getIdentifier() + ".defaultpreferenceinitializer";
-   }
-
-   /**
     * Gets the {@IPreferenceStore} implementation of the {@link CatPlugin}.
     * 
     * @return the {@link CatPlugin} {@link IPreferenceStore} instance.
@@ -75,13 +178,75 @@
    }
 
    /**
-    * Saves the {@link CatPlugin} OSGi bundle symbolic name set in the MANIFEST.MF as the plug-in identifier. This
-    * member will remain <code>null</code> when the bundle symbolic name cannot be determined.
+    * Gets a {@link File} handle for the plug-in state file. The state file is used to persist this
+    * {@link ProjectManager} cache. The file pointed to by the handle may not exist. This method will create the state
+    * location folder if it does not exist.
     * 
-    * @implNote This member is "lazy loaded" by the static method {@link CatPlugin#getIdentifier}.
+    * @return a {@link File} handle for the plug-in state file.
     */
 
-   private String catPluginIdentifier;
+   public static File getStateLocationFile() {
+      File stateLocationFile = CatPlugin.instance.getStateLocation().append(Constants.catPluginStateFile).toFile();
+      return stateLocationFile;
+   }
+
+   /**
+    * Saves the CAT Plug-In's preferences to file.
+    * 
+    * @throws CatPluginException when unable to persist the preference store.
+    */
+
+   public static void savePreferences() {
+      assert Objects.nonNull(CatPlugin.instance) : "CatPlugin instance is unexpectedly null.";
+      try {
+         InstanceScope.INSTANCE.getNode(CatPlugin.getIdentifier()).flush();
+      } catch (BackingStoreException e) {
+         //@formatter:off
+         CatPluginException preferenceFileSaveException =
+            new CatPluginException
+                   (
+                      CatErrorCode.PreferenceFileSaveError,
+                      "Failed to save the CAT Plug-In preferences file.",
+                      e
+                   );
+         //@formatter:on
+         throw preferenceFileSaveException;
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException preferenceFileSaveException =
+            new CatPluginException
+                   (
+                      CatErrorCode.PreferenceFileSaveError,
+                      "Failed to save the CAT Plug-In preferences file."
+                   );
+         //@formatter:on
+         throw preferenceFileSaveException;
+      }
+   }
+
+   /**
+    * Saves the extension identifier for the CAT Plug-In project nature.
+    */
+
+   private final String catNatureIdentifier;
+
+   /**
+    * Saves the {@link CatPlugin} OSGi bundle symbolic name set in the MANIFEST.MF as the plug-in identifier.
+    */
+
+   private final String catPluginIdentifier;
+
+   /**
+    * Saves the {@link CatProjectManager} instance.
+    */
+
+   private final CatProjectManager catProjectManager;
+
+   /**
+    * Saves the command line option name used for the default preferences file.
+    */
+
+   private final String defaultPreferencesCommandLineOptionName;
 
    /**
     * Creates an instance of the {@link CatPlugin} and sets the static instance reference.
@@ -95,10 +260,13 @@
       CatPlugin.instance = this;
       Bundle bundle = this.getBundle();
       this.catPluginIdentifier = bundle.getSymbolicName();
+      this.catNatureIdentifier = CatPlugin.findCatNatureIdentifier();
+      this.defaultPreferencesCommandLineOptionName = CatPlugin.findDefaultPreferencesCommandLineOptionName();
+      this.catProjectManager = new CatProjectManager();
    }
 
    /**
-    * Bundle activator method for the {@link CatPlugin}. The {@link AbstractUIPlugin#start} method is called to enable
+    * Bundle activation method for the {@link CatPlugin}. The {@link AbstractUIPlugin#start} method is called to enable
     * the base class features for the plug-in.
     * <p>
     * {@inheritDoc}
@@ -112,21 +280,27 @@
       try {
 
          super.start(context);
+         Policy.setErrorSupportProvider(new CatErrorSupportProvider());
+         this.catProjectManager.start();
+         CatErrorCode.verifyStatusCodes();
 
+      } catch (CatPluginException cpe) {
+         /*
+          * Internal exceptions are caught and re-thrown to prevent them from getting wrapped into another
+          * CatPluginException.
+          */
+         throw cpe;
       } catch (Exception e) {
 
          //@formatter:off
          CatPluginException startException =
             new CatPluginException
                    (
-                      StatusManager.LOG,
-                      "CAT Plugin Activator Error",
-                      Status.ERROR,
+                      CatErrorCode.InternalError,
                       "CAT Plugin Activator failed to start.",
                       e
                    );
          //@formatter:on
-         startException.log();
 
          throw startException;
       }
@@ -134,8 +308,8 @@
    }
 
    /**
-    * Bundle deactivator method for the {@link CatPlugin}. The {@link AbstractUIPlugin#stop} method is called to provide
-    * an orderly shut down of the plug-in.
+    * Bundle deactivation method for the {@link CatPlugin}. The {@link AbstractUIPlugin#stop} method is called to
+    * provide an orderly shut down of the plug-in.
     * <p>
     * {@inheritDoc}
     * 
@@ -147,6 +321,7 @@
 
       try {
 
+         this.catProjectManager.stop();
          super.stop(context);
 
       } catch (Exception e) {
@@ -155,9 +330,7 @@
          CatPluginException stopException =
             new CatPluginException
                    (
-                      StatusManager.LOG,
-                      "CAT Plugin Activator Error",
-                      Status.ERROR,
+                      CatErrorCode.InternalError,
                       "CAT Plugin Activator failed to stop.",
                       e
                    );
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/CatPluginException.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/CatPluginException.java
deleted file mode 100644
index 6e0b496..0000000
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/CatPluginException.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*********************************************************************
- * Copyright (c) 2024 Boeing
- * 
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * https://www.eclipse.org/legal/epl-2.0/
- * 
- * SPDX-License-Identifier: EPL-2.0
- * 
- * Contributors: Boeing - initial API and implementation
- **********************************************************************/
-
-package org.eclipse.ote.cat.plugin;
-
-import java.util.Objects;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.ui.statushandlers.IStatusAdapterConstants;
-import org.eclipse.ui.statushandlers.StatusAdapter;
-import org.eclipse.ui.statushandlers.StatusManager;
-
-/**
- * The {@link RuntimeException} class used by classes in the CAT Plugin.
- * 
- * @author Loren K. Ashley
- */
-
-public class CatPluginException extends RuntimeException {
-
-   /**
-    * The {@link IStatus} implementation class used when reporting the {@link CatPluginException} to the
-    * {@link StatusManager}.
-    */
-
-   private class CatPluginStatus extends Status {
-
-      /**
-       * Save the linked child {@link IStatus} object. <code>null</code> is a sentinel value to indicate that this
-       * status does not have a child.
-       */
-
-      private final IStatus child;
-
-      /**
-       * Creates a new {@link CatPluginStatus}. When the <code>cause</code> exception is a {@link CatPluginException} or
-       * a {@link CoreException}, the {@link IStatus} from the <code>cause</code> exception is linked as the
-       * {@link #child} of this status.
-       *
-       * @param severity the {@link IStatus} severity of the error. @see {@link IStatus#ERROR ERROR},
-       * {@link IStatus#WARNING WARNING}, and {@link IStatus#INFO INFO}.
-       * @param code the plug-in-specific status code, or <code>OK</code>
-       * @param message a detailed message describing the exception. When <code>null</code> the message
-       * {@value #undescribedMessage} will be used.
-       * @param cause the causing exception. This parameter may be <code>null</code>.
-       */
-
-      private CatPluginStatus(int severity, int code, String message, Throwable cause) {
-         super(severity, CatPlugin.getIdentifier(), code, CatPluginException.getSafeMessage(message), cause);
-         //@formatter:off
-         this.child =
-            Objects.isNull(cause)
-               ? null
-               : (cause instanceof CatPluginException)
-                    ? ((CatPluginException) cause).status
-                    : (cause instanceof CoreException)
-                         ? ((CoreException) cause).getStatus()
-                         : null;
-         //@formatter:on
-         if (Objects.nonNull(child)) {
-            int maxSeverity = Math.max(severity, child.getSeverity());
-            if (maxSeverity > severity) {
-               this.setSeverity(maxSeverity);
-            }
-         }
-      }
-
-      /**
-       * {@inheritDoc}
-       */
-
-      @Override
-      public IStatus[] getChildren() {
-         if (Objects.nonNull(child)) {
-            IStatus[] result = new IStatus[1];
-            result[0] = this.child;
-            return result;
-         }
-         return new IStatus[0];
-      }
-
-      /**
-       * {@inheritDoc}
-       */
-
-      @Override
-      public boolean isMultiStatus() {
-         return Objects.nonNull(this.child);
-      }
-
-   }
-
-   /**
-    * The error dialog title to use when a title was not provided for the {@link CatPluginException}.
-    */
-
-   private static final String defaultTitle = "CAT Plug-in Error";
-
-   /**
-    * A sentinel value used to indicate that a cause exception is not available.
-    */
-
-   public static final Throwable noCause = null;
-
-   /**
-    * The serialization version identifier;
-    */
-
-   private static final long serialVersionUID = 0L;
-
-   /**
-    * The exception message used with a message was not provided for the {@link CatPluginException}.
-    */
-
-   private static final String undescribedMessage = "Undescribed Exception";
-
-   /**
-    * Gets a non-<code>null</code> message.
-    * 
-    * @param unsafeMessage the message provided to the {@link CatPluginException} which may be <code>null</code>.
-    * @return <code>unsafeMessage</code> when <code>unsafeMessage</code> is non-<code>null</code>; otherwise,
-    * {@value #undescribedMessage}.
-    */
-
-   private static String getSafeMessage(String unsafeMessage) {
-      return Objects.nonNull(unsafeMessage) ? unsafeMessage : CatPluginException.undescribedMessage;
-   }
-
-   /**
-    * Gets a non-<code>null</code> title.
-    * 
-    * @param unsafeTitle the title provided to the {@link CatPluginException} which may be <code>null</code>.
-    * @return <code>unsafeTitle</code> when <code>unsafeTitle</code> is non-<code>null</code>; otherwise,
-    * {@value #defaultTitle}.
-    */
-
-   private static String getSafeTitle(String unsafeTitle) {
-      return Objects.nonNull(unsafeTitle) ? unsafeTitle : CatPluginException.defaultTitle;
-   }
-
-   /**
-    * Saves the {@link IStatus} implementation created from the constructor parameters. This is the {@link IStatus}
-    * object passed to the {@link StatusManager} by the {@link #log} method.
-    */
-
-   private final IStatus status;
-
-   /**
-    * Saves the {@link StatusManager} style bits. @see {@link StatusManager#BLOCK BLOCK}, {@link StatusManager#SHOW
-    * SHOW}, and {@link StatusManager#LOG LOG}.
-    */
-
-   private int style;
-
-   /**
-    * Saves the title to be used for the error dialog.
-    */
-
-   private final String title;
-
-   /**
-    * Creates a new runtime {@link CatPluginException}. When the <code>cause</code> exception is either a
-    * {@link CatPluginException} or a {@link CoreException}, the {@link IStatus} object from the <code>cause</code>
-    * exception will be linked as the child for the {@link IStatus} created for this exception.
-    * 
-    * @param style the {@link StatusManager} display bits. @see {@link StatusManager#BLOCK BLOCK},
-    * {@link StatusManager#SHOW SHOW}, and {@link StatusManager#LOG LOG}.
-    * @param title the title used for the error dialog. When <code>null</code> the default title {@value #defaultTitle}
-    * will be used.
-    * @param severity the {@link IStatus} severity of the error. @see {@link IStatus#ERROR ERROR},
-    * {@link IStatus#WARNING WARNING}, and {@link IStatus#INFO INFO}.
-    * @param message a detailed message describing the exception. When <code>null</code> the message
-    * {@value #undescribedMessage} will be used.
-    * @param cause the causing exception. This parameter may be <code>null</code>.
-    */
-
-   public CatPluginException(int style, String title, int severity, String message, Throwable cause) {
-      super(CatPluginException.getSafeMessage(message));
-      if (Objects.nonNull(cause)) {
-         this.initCause(cause);
-      }
-      this.status = new CatPluginStatus(severity, 0, message, cause);
-      this.style = style;
-      this.title = CatPluginException.getSafeTitle(title);
-   }
-
-   /**
-    * Logs the provided <code>status</code> with the {@link StatusManager}.
-    * 
-    * @param status the {@link IStatus} to be logged.
-    */
-
-   public void log() {
-
-      StatusAdapter statusAdapter = new StatusAdapter(this.status);
-      statusAdapter.setProperty(IStatusAdapterConstants.TITLE_PROPERTY, this.title);
-      statusAdapter.setProperty(IStatusAdapterConstants.TIMESTAMP_PROPERTY, System.currentTimeMillis());
-      StatusManager.getManager().handle(statusAdapter, this.style);
-   }
-
-}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/Constants.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/Constants.java
new file mode 100644
index 0000000..42e0034
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/Constants.java
@@ -0,0 +1,164 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin;
+
+import javax.annotation.processing.Processor;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.ote.cat.plugin.preferencepage.Preference;
+import org.eclipse.ote.cat.plugin.project.CatNature;
+import org.eclipse.ote.cat.plugin.project.CatProjectManager;
+
+/**
+ * This class contains "configuration as code" constants for the CAT Plug-In.
+ * 
+ * @author Loren K. Ashley
+ * @implNote The preference store names of the plug-in preferences are also used as JSON object names in the default
+ * preferences file. They are defined as constants so that they may be used as {@link com.fasterxml.jackson.annotation}
+ * values. For general access, the method {@link Preference#getPreferenceStoreName} should be used to obtain the
+ * preference store names.
+ */
+
+public class Constants {
+
+   /**
+    * When deconfiguring a project for the {@link CatNature} annotation processor Jar file names are compared to this
+    * string in a case insensitive manner to identify a CAT annotation processor Jar file. This is done because the
+    * {@link Preference#CAT_JAR} may no longer point to the annotation Jar file that needs to be removed.
+    */
+
+   public static final String catJarDetectionName = "cat.jar";
+
+   /**
+    * The preference store name used to save the location of the CAT annotation processor Jar file.
+    */
+
+   public static final String catJarPreferenceStoreName = "CAT_JAR_PATH";
+
+   /**
+    * The name of the CAT Plug-In's state file. This file is used to persist the {@link CatProjectManager} cache.
+    */
+
+   public static final String catPluginStateFile = "state.dat";
+
+   /**
+    * A description string for the CAT Plug-In state file.
+    */
+
+   public static final String catPluginStateFileDescription = "CAT Plug-In State File";
+
+   /**
+    * The CAT annotation processor identifier. This needs to be set to the {@link Processor} implementation class name
+    * of the CAT annotation processor. This should also match the contents of the
+    * "META-INF/services/javax.annotation.processing.Processor" file in the CAT annotation processor Jar file.
+    */
+
+   public static final String catProcessorIdentifier = "org.eclipse.ote.cat.CatProcessor";
+
+   /**
+    * The name of the file used to save the CAT project settings for an Eclipse {@link IProject} that has the
+    * {@link CatNature} applied.
+    */
+
+   public static final String catProjectInfoFileName = ".catproject";
+
+   /**
+    * A description string for CAT project settings files.
+    */
+
+   public static final String catProjectInfoFileDescription = "CAT Project Info File";
+
+   /**
+    * The preference store name used to save the list of Java Test Script projects that are configured for the CAT
+    * annotation processor.
+    */
+
+   public static final String jtsProjectsPreferenceStoreName = "JTS_PROJECTS";
+
+   /**
+    * The OSGi extension point identifier for project natures.
+    */
+
+   public static final String naturesExtensionPointIdentifier = "org.eclipse.core.resources.natures";
+
+   /**
+    * The preference store name used to save the path to the folder used to cache PLE Configurations downloaded from the
+    * OPLE server.
+    */
+
+   public static final String pleConfigurationCacheFolderPreferenceStoreName = "PLE_CONFIGURATION_CACHE_FOLDER";
+
+   /**
+    * The preference store name used to save the URL of the OPLE server.
+    */
+
+   public static final String pleConfigurationLoaderPreferenceStoreName = "OPLE_SERVER";
+
+   /**
+    * The CAT annotation processor command option for the PLE Configuration file path:
+    * <ul>
+    * <li>{@value #pleConfigurationPathCatOption}</li>
+    * </ul>
+    */
+
+   public static final String pleConfigurationPathCatOption = "org.eclipse.ote.cat.pleconfigurationpath";
+
+   /**
+    * The preference store name used to save the PLE Configuration to be used by the CAT annotation processor.
+    */
+
+   public static final String pleConfigurationPreferenceStoreName = "PLE_CONFIGURATION";
+
+   /**
+    * The name of the {@value #preferencesInitializerConfigurationElement} configuration element of the
+    * {@value #preferencesExtensionPointIdentifier} extension point that specifies the command line option name for the
+    * default preferences file.
+    */
+
+   public static final String preferencesCommandLineOptionAttributeName = "option";
+
+   /**
+    * The OSGi extension point identifier for preference initializers.
+    */
+
+   public static final String preferencesExtensionPointIdentifier = "org.eclipse.core.runtime.preferences";
+
+   /**
+    * The name of the configuration element of the {@value #preferencesExtensionPointIdentifier} extension point that
+    * contains the command line option name for the default preferences file.
+    */
+
+   public static final String preferencesInitializerConfigurationElement = "initializer";
+
+   /**
+    * The CAT annotation processor command option for the method to location source files:
+    * <ul>
+    * <li>{@value #sourceLocationMethodCatOption}</li>
+    * </ul>
+    */
+
+   public static final String sourceLocationMethodCatOption = "org.eclipse.ote.cat.sourcelocationmethod";
+
+   /**
+    * The preference store name used to save the source location method for the CAT annotation processor.
+    */
+
+   public static final String sourceLocationMethodPreferenceStoreName = "SOURCE_LOCATION_METHOD";
+
+   /**
+    * Constructor is private to prevent instantiation of the class.
+    */
+
+   private Constants() {
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/Startup.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/Startup.java
new file mode 100644
index 0000000..cab5706
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/Startup.java
@@ -0,0 +1,36 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin;
+
+import org.eclipse.ui.IStartup;
+
+/**
+ * CAT Plug-In class that is loaded once the workspace is ready which will cause the CAT Plug-In bundle to be activated.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class Startup implements IStartup {
+
+   /**
+    * Invocation of this method will cause the CAT Plug-In bundle to be activated. The method itself performs no action.
+    * <p>
+    * {@inheritDoc}
+    */
+
+   @Override
+   public void earlyStartup() {
+      //No action necessary
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/AddRemoveBox.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/AddRemoveBox.java
new file mode 100644
index 0000000..6a6fd5e
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/AddRemoveBox.java
@@ -0,0 +1,186 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.composites;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Supplier;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * A {@link Composite} extension containing a {@link TableViewer}.
+ *
+ * @param <T> the type of object displayed in the selection table.
+ */
+
+public class AddRemoveBox<T> extends Composite {
+
+   /**
+    * A functional interface for the callback that is invoked when the table's selection is changed.
+    */
+
+   @FunctionalInterface
+   public interface SelectionChangedAction {
+
+      /**
+       * Callback method for a new table selection.
+       */
+
+      public void selectionChanged();
+   }
+
+   /**
+    * Saves the callback for a table selection change.
+    */
+
+   private SelectionChangedAction selectionChangedAction;
+
+   /**
+    * Saves the {@link TableViewer} displayed in the {@link Composite}.
+    */
+
+   private TableViewer tableViewer;
+
+   /**
+    * Creates a new {@link Composite} containing a {@link TableViewer}.
+    * 
+    * @param parent the {@link Composite} the selection box is to be attached to.
+    * @param selectionChangedAction this callback is invoked when the table selection changes.
+    */
+
+   public AddRemoveBox(Composite parent, SelectionChangedAction selectionChangedAction) {
+
+      super(parent, SWT.NULL);
+
+      GridLayout gridLayout = new GridLayout();
+      gridLayout.marginWidth = 0;
+
+      this.setLayout(gridLayout);
+
+      GridData childGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
+      childGridData.heightHint = 300;
+      childGridData.widthHint = 300;
+
+      this.tableViewer = new TableViewer(this);
+      Composite childComposite = this.tableViewer.getTable();
+      childComposite.setLayoutData(childGridData);
+
+      this.selectionChangedAction = selectionChangedAction;
+      //@formatter:off
+      ISelectionChangedListener selectionChangedListener =
+         new ISelectionChangedListener() {
+
+         @Override
+         public void selectionChanged(SelectionChangedEvent event) {
+            AddRemoveBox.this.selectionChangedAction.selectionChanged();
+         }
+      
+      };
+      //@formatter:on
+      this.tableViewer.addSelectionChangedListener(selectionChangedListener);
+      this.addDisposeListener(this::dispose);
+   }
+
+   /**
+    * Releases operating system resources for the table.
+    * 
+    * @param disposeEvent unused
+    */
+
+   public void dispose(DisposeEvent disposeEvent) {
+      this.tableViewer.getTable().dispose();
+      this.tableViewer = null;
+   }
+
+   /**
+    * Adds the elements of the <code>items</code> {@link List} to the table.
+    * 
+    * @param items a {@link List} of the Class&lt;T&gt; items to be added.
+    */
+
+   public void add(List<T> items) {
+      Set<T> currentContents = this.getContents(HashSet::new);
+      items.stream().filter((project) -> !currentContents.contains(project)).forEach(this.tableViewer::add);
+   }
+
+   /**
+    * Gets a {@link Collection} of the Class&lt;T&gt; items held in the selection table.
+    * 
+    * @param <C> the type of collection created and returned by the method.
+    * @param collectionFactory a {@link Supplier} for the {@link Collection} to be filled by the method.
+    * @return the {@link Collection} provided by the <code>collectionFactory</code> {@link Supplier}.
+    */
+
+   public <C extends Collection<T>> C getContents(Supplier<C> collectionFactory) {
+      int i;
+      C selections = collectionFactory.get();
+      //@formatter:off
+      for( Object element = this.tableViewer.getElementAt(i=0);
+           Objects.nonNull( element );
+           element = this.tableViewer.getElementAt(++i)) {
+         @SuppressWarnings("unchecked")
+         T t = (T) element;
+         selections.add( t );
+      }
+      return selections;
+      
+   }
+
+   /**
+    * Gets a {@link List} of the currently selected items in the table viewer.
+    * 
+    * @return a {@link List} of the selected items.
+    */
+   
+   public List<T> getSelected() {
+      IStructuredSelection structuredSelection = this.tableViewer.getStructuredSelection();
+      @SuppressWarnings("unchecked")
+      List<T> selected = structuredSelection.toList();
+      return selected;
+   }
+
+   /**
+    * Predicate to determine if at least one item is selected in the table viewer.
+    * 
+    * @return <code>true</code> when an item is selected; otherwise, <code>false</code>.
+    */
+
+   public boolean hasSelection() {
+      ISelection selection = this.tableViewer.getSelection();
+      boolean result = !selection.isEmpty();
+      return result;
+   }
+
+   /**
+    * Removes the elements of the <code>items</code> {@link List} from the table viewer.
+    * 
+    * @param items a {@link List} of the Class&lt;T&gt; items to be removed.
+    */
+   
+   public void remove(List<T> items) {
+      this.tableViewer.remove(items.toArray());
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/ButtonBox.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/ButtonBox.java
index d1fb964..77f4b5a 100644
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/ButtonBox.java
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/ButtonBox.java
@@ -117,20 +117,20 @@
     * @param enableMaskSupplier this callback is invoked when enabling or disabling buttons.
     */
 
-   public ButtonBox(Composite parent, ButtonAction addButtonAction, ButtonAction removeButtonAction, EnableMaskSupplier enableAction) {
+   public ButtonBox(Composite parent, ButtonAction addButtonAction, ButtonAction removeButtonAction, EnableMaskSupplier enableMaskSupplier) {
 
       super(parent, SWT.NULL);
 
       this.addButtonAction = addButtonAction;
       this.removeButtonAction = removeButtonAction;
-      this.enableMaskSupplier = enableAction;
+      this.enableMaskSupplier = enableMaskSupplier;
 
       GridLayout gridLayout = new GridLayout();
       gridLayout.marginWidth = 0;
 
       this.setLayout(gridLayout);
       this.createButtons();
-      this.addDisposeListener(this::disposer);
+      this.addDisposeListener(this::dispose);
       this.enableButtons();
    };
 
@@ -152,7 +152,7 @@
     * <li>sets up a selection listener with the <code>buttonAction</code>.</li>
     * </ul>
     * 
-    * @param key the resource name used to supply the button's label text.
+    * @param label the resource name used to supply the button's label text.
     * @param buttonAction the callback to be invoked with the button is pressed.
     * @return Button the created button.
     */
@@ -176,7 +176,7 @@
     * @param event unused
     */
 
-   private void disposer(DisposeEvent event) {
+   private void dispose(DisposeEvent event) {
       this.addButton.dispose();
       this.addButton = null;
       this.removeButton.dispose();
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/CatBrowserErrorSupport.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/CatBrowserErrorSupport.java
new file mode 100644
index 0000000..bfb4e0e
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/CatBrowserErrorSupport.java
@@ -0,0 +1,64 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.composites;
+
+import java.net.URL;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * A {@link Composite} extension containing a browser used to display user support information about an exception.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class CatBrowserErrorSupport extends Composite {
+
+   /**
+    * Creates a new {@link Composite} containing a browser.
+    * 
+    * @param parent the {@link Composite} the browser is attached to.
+    * @param url the initial URL to be displayed in the browser.
+    */
+
+   public CatBrowserErrorSupport(Composite parent, URL url) {
+      super(parent, SWT.NONE);
+      this.browser = new Browser(parent, SWT.NONE);
+      GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
+      browser.setLayoutData(gridData);
+      browser.setUrl(url.toExternalForm());
+
+      browser.addDisposeListener(this::dispose);
+   }
+
+   /**
+    * Saves the {@link Control} for the browser.
+    */
+
+   private Browser browser;
+
+   /**
+    * Releases operating system resources for the browser.
+    * 
+    * @param disposeEvent unused
+    */
+
+   private void dispose(DisposeEvent disposeEvent) {
+      this.browser.dispose();
+      this.browser = null;
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/CatMessageErrorSupport.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/CatMessageErrorSupport.java
new file mode 100644
index 0000000..74fb967
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/CatMessageErrorSupport.java
@@ -0,0 +1,186 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.composites;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * A {@link Composite} extension to display an exception message and trace.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class CatMessageErrorSupport extends Composite {
+
+   /**
+    * Label for the trace display button when the trace is visible.
+    */
+
+   private static final String closeTraceLabel = "<< Debug Trace";
+
+   /**
+    * Label for the trace display button when the trace is not visible.
+    */
+
+   private static final String openTraceLabel = "Debug Trace >>";
+
+   /**
+    * Saves the {@link Text} {@link Control} used to display the exception message.
+    */
+
+   private Text message;
+
+   /**
+    * Saves the {@link Text} {@link Control} used to display the exception title.
+    */
+
+   private Text title;
+
+   /**
+    * Saves the {@link Text} {@link Control} used to display the exception trace.
+    */
+
+   private Text trace;
+
+   /**
+    * Saves the {@link Button} {@link Control} used to toggle visibility of the exception trace.
+    */
+
+   private Button traceButton;
+
+   /**
+    * Creates a new {@link Composite} used to display an exception message and trace.
+    * 
+    * @param parent the {@link Composite} the exception display is attached to.
+    * @param title a title for the exception display.
+    * @param message the exception message.
+    * @param trace the exception stack trace.
+    */
+
+   public CatMessageErrorSupport(Composite parent, String title, String message, String trace) {
+
+      super(parent, SWT.NONE);
+      this.addDisposeListener(this::dispose);
+
+      /*
+       * Title Viewer
+       */
+
+      GridData titleGridData = new GridData();
+
+      titleGridData.horizontalAlignment = SWT.FILL;
+      titleGridData.grabExcessHorizontalSpace = true;
+      titleGridData.minimumWidth = 128;
+      titleGridData.widthHint = 128;
+
+      titleGridData.verticalAlignment = SWT.BEGINNING;
+      titleGridData.grabExcessVerticalSpace = false;
+
+      this.title = new Text(parent, SWT.WRAP);
+      this.title.setLayoutData(titleGridData);
+      this.title.setText(title);
+
+      /*
+       * Message Viewer
+       */
+
+      GridData messageGridData = new GridData();
+
+      messageGridData.horizontalAlignment = SWT.FILL;
+      messageGridData.grabExcessHorizontalSpace = true;
+      messageGridData.minimumWidth = 128;
+      messageGridData.widthHint = 128;
+
+      messageGridData.verticalAlignment = SWT.BEGINNING;
+      messageGridData.grabExcessVerticalSpace = false;
+
+      FontData messageFontData = new FontData("Courier", 10, SWT.NORMAL);
+      Font messageFont = new Font(parent.getDisplay(), messageFontData);
+
+      this.message = new Text(parent, SWT.WRAP);
+      this.message.setLayoutData(messageGridData);
+      this.message.setFont(messageFont);
+      this.message.setText(message);
+
+      /*
+       * Details Button
+       */
+
+      this.traceButton = new Button(parent, SWT.NONE);
+      SelectionListener buttonSelectionListener = SelectionListener.widgetSelectedAdapter((event) -> buttonPress());
+      this.traceButton.addSelectionListener(buttonSelectionListener);
+      this.traceButton.setText(openTraceLabel);
+
+      /*
+       * Stack trace viewer
+       */
+
+      GridData traceGridData = new GridData();
+
+      traceGridData.horizontalAlignment = SWT.FILL;
+      traceGridData.grabExcessHorizontalSpace = true;
+      traceGridData.minimumWidth = 128;
+      traceGridData.widthHint = 128;
+
+      traceGridData.verticalAlignment = SWT.FILL;
+      traceGridData.grabExcessVerticalSpace = true;
+      traceGridData.minimumHeight = 256;
+      traceGridData.heightHint = 256;
+
+      this.trace = new Text(parent, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
+      this.trace.setLayoutData(traceGridData);
+      this.trace.setEditable(false);
+      this.trace.setText(trace);
+      this.trace.setVisible(false);
+   }
+
+   /**
+    * Call back method for {@link traceButton} presses. When the exception trace is visible it will be made invisible
+    * and vice-versa.
+    */
+
+   private void buttonPress() {
+      if (this.trace.isVisible()) {
+         this.traceButton.setText(openTraceLabel);
+         this.trace.setVisible(false);
+      } else {
+         this.traceButton.setText(closeTraceLabel);
+         this.trace.setVisible(true);
+      }
+   }
+
+   /**
+    * Releases operating system resources for the error support message controls.
+    * 
+    * @param disposeEvent unused
+    */
+
+   private void dispose(DisposeEvent disposeEvent) {
+      this.title.dispose();
+      this.title = null;
+      this.message.dispose();
+      this.message = null;
+      this.traceButton.dispose();
+      this.traceButton = null;
+      this.trace.dispose();
+      this.trace = null;
+   }
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/SelectionBox.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/SelectionBox.java
deleted file mode 100644
index b78aae4..0000000
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/composites/SelectionBox.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*********************************************************************
- * Copyright (c) 2024 Boeing
- * 
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * https://www.eclipse.org/legal/epl-2.0/
- * 
- * SPDX-License-Identifier: EPL-2.0
- * 
- * Contributors: Boeing - initial API and implementation
- **********************************************************************/
-
-package org.eclipse.ote.cat.plugin.composites;
-
-import java.util.List;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-
-/**
- * A {@link Composite} extension containing a {@link TableViewer}.
- *
- * @param <T> the type of object displayed in the selection table.
- */
-
-public class SelectionBox<T> extends Composite {
-
-   /**
-    * A functional interface for the callback that is invoked when the table's selection is changed.
-    */
-
-   @FunctionalInterface
-   public interface SelectionChangedAction {
-
-      /**
-       * Callback method for a new table selection.
-       */
-
-      public void selectionChanged();
-   }
-
-   /**
-    * Saves the callback for a table selection change.
-    */
-
-   private SelectionChangedAction selectionChangedAction;
-
-   /**
-    * Saves the {@link TableViewer} displayed in the {@link Composite}.
-    */
-
-   private TableViewer tableViewer;
-
-   /**
-    * Creates a new {@link Composite} containing a {@link TableViewer}.
-    * 
-    * @param parent the {@link Composite} the selection box is to be attached to.
-    * @param selectionChangedAction this callback is invoked when the table selection changes.
-    */
-
-   public SelectionBox(Composite parent, SelectionChangedAction selectionChangedAction) {
-
-      super(parent, SWT.NULL);
-
-      GridLayout gridLayout = new GridLayout();
-      gridLayout.marginWidth = 0;
-
-      this.setLayout(gridLayout);
-
-      GridData childGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
-      childGridData.heightHint = 300;
-      childGridData.widthHint = 300;
-
-      this.tableViewer = new TableViewer(this);
-      Composite childComposite = this.tableViewer.getTable();
-      childComposite.setLayoutData(childGridData);
-
-      this.selectionChangedAction = selectionChangedAction;
-      //@formatter:off
-      ISelectionChangedListener selectionChangedListener =
-         new ISelectionChangedListener() {
-
-         @Override
-         public void selectionChanged(SelectionChangedEvent event) {
-            SelectionBox.this.selectionChangedAction.selectionChanged();
-         }
-      
-      };
-      //@formatter:on
-      this.tableViewer.addSelectionChangedListener(selectionChangedListener);
-   }
-
-   /**
-    * Adds the elements of the <code>items</code> {@link List} to the table.
-    * 
-    * @param items a {@link List} of the Class&lt;T&gt; items to be added.
-    */
-
-   public void add(List<T> items) {
-      this.tableViewer.add(items.toArray());
-   }
-
-   /**
-    * Predicate to determine if an item is selected in the table.
-    * 
-    * @return <code>true</code> when an item is selected; otherwise, <code>false</code>.
-    */
-
-   public boolean hasSelection() {
-      ISelection selection = this.tableViewer.getSelection();
-      boolean result = !selection.isEmpty();
-      return result;
-   }
-
-}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/exception/CatErrorCode.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/exception/CatErrorCode.java
new file mode 100644
index 0000000..6cc6285
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/exception/CatErrorCode.java
@@ -0,0 +1,541 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.exception;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.osee.framework.core.util.OseeInf;
+import org.eclipse.ui.statushandlers.StatusManager;
+
+/**
+ * An enumeration of error codes used specify the following for CAT Plug-In exceptions:
+ * <ul stype="margin-left:3em;">
+ * <li>The {@link StatusManager} style bits used to determine if an error dialog is displayed or if the error is
+ * logged.</li>
+ * <li>The {@link IStatus} severity of the error.</li>
+ * <li>A unique integer identifier for the error.</li>
+ * <li>The {@link CatErrorCode#Type} used to determine the {@link Control} used to display user support information on
+ * the error dialog.</li>
+ * </ul>
+ * 
+ * @author Loren K. Ashley
+ */
+
+public enum CatErrorCode {
+
+   //@formatter:off
+   /*
+    * Status Codes
+    * 
+    * The lower word (short) of the status code is provided to the CatErrorCode constructor. The upper word (short) is
+    * obtained from the Type. For CatErrorCode members with the same Type the lower status code word must be unique.
+    * When creating new CatErrorCode members use the next status code for the type according to the table below and increment
+    * the value in the table. Don't change existing status codes or reuse status code numbers below the numbers in the table.
+    * 
+    * Last Status Code By Type:
+    * 
+    *    Type.UserError:     9
+    *    Type.InternalError: 3
+    */
+   //@formatter:on
+
+   //@formatter:off
+
+   CatJarFileError
+      (
+         Type.UserError,
+         StatusManager.BLOCK | StatusManager.LOG,
+         IStatus.ERROR,
+         (short) 0x0001,
+         "CAT Jar File Error",
+         "cat-jar-file-error.html"
+      ),
+      
+   CatPluginStateFileError
+      (
+         Type.UserError,
+         StatusManager.BLOCK | StatusManager.LOG, 
+         IStatus.ERROR, 
+         (short) 0x0002, 
+         "CAT Plug-In State File Error", 
+         "cat-plugin-state-file-error.html"
+      ),
+
+   CatProjectInfoFileError
+      (
+         Type.UserError,
+         StatusManager.BLOCK | StatusManager.LOG,
+         IStatus.ERROR,
+         (short) 0x0003,
+         "CAT Project Info File Error",
+         "cat-project-info-file-error.html"
+      ),
+      
+   CommandLineOptionError
+      (
+         Type.UserError,
+         StatusManager.BLOCK | StatusManager.LOG,
+         IStatus.ERROR,
+         (short) 0x0004,
+         "CAT Plug-In Command Line Option Error",
+         "command-line-option-error.html"
+      ),
+      
+   PreferenceFileError
+      (
+         Type.UserError,
+         StatusManager.BLOCK | StatusManager.LOG,
+         IStatus.ERROR,
+         (short) 0x0005,
+         "CAT Plug-In Default Preference File Error",
+         "default-preference-file-error.html"
+      ),
+      
+   InternalError
+      (
+         Type.InternalError, 
+         StatusManager.BLOCK | StatusManager.LOG,
+         IStatus.ERROR,
+         (short) 0x0001,
+         "CAT Plug-In Internal Error",
+         null
+      ),
+      
+   InvalidCatErrorCode
+      (
+         Type.InternalError,
+         StatusManager.BLOCK | StatusManager.LOG,
+         IStatus.ERROR,
+         (short) 0x0002,
+         "CAT Plug-In \"CatErrorCode\" Error",
+         null
+      ),
+
+   NoDefaultPreferencesWarning
+      (
+         Type.UserError,
+         StatusManager.LOG,
+         IStatus.INFO,
+         (short) 0x0006,
+         "Default Preferences Command Line Option Warning",
+         "no-default-preferences-warning.html"
+      ),
+   
+   PleConfigurationCacheFolderError
+      (
+         Type.UserError,
+         StatusManager.BLOCK | StatusManager.LOG,
+         IStatus.ERROR,
+         (short) 0x0007,
+         "PLE Configuration Cache Folder Error",
+         "ple-configuration-cache-folder-error.html"
+      ),
+   
+   PreferenceFileSaveError
+      (
+         Type.UserError,
+         StatusManager.BLOCK | StatusManager.LOG,
+         IStatus.ERROR,
+         (short) 0x0008,
+         "CAT Preference File Error",
+         "preference-file-save-error.html"
+      ),
+      
+    PreferencePageError
+       (
+          Type.InternalError,
+          StatusManager.BLOCK | StatusManager.LOG,
+          IStatus.ERROR,
+          (short) 0x0003,
+          "CAT Plug-In Preference Page Error",
+          null
+       ),
+       
+    SourceLocationMethodError
+       (
+          Type.UserError,
+          StatusManager.BLOCK | StatusManager.LOG,
+          IStatus.ERROR,
+          (short) 0x0009,
+          "CAT Source Location Method Error",
+          "source-location-method-error.html"
+       );
+      
+   //@formatter:on
+
+   /**
+    * The type of error. Used by the {@link CatErrorSupportProvider} to select the {@link Control} for the user support
+    * area of the {@link StatusManager} error dialog.
+    */
+
+   enum Type {
+
+      /**
+       * Errors of this type are likely not resolvable by the user.
+       */
+
+      InternalError((short) 0x0001),
+
+      /**
+       * Errors of this type are possibly resolvable by the user.
+       */
+
+      UserError((short) 0x0000);
+
+      /**
+       * Saves the high word used for a {@link CatErrorCode} status code.
+       */
+
+      private short statusCodeHighWord;
+
+      /**
+       * Creates a new {@link Type} enumeration member.
+       * 
+       * @param statusCodeHighWord the high word for {@link CatErrorCode}s of the type.
+       */
+
+      Type(short statusCodeHighWord) {
+         this.statusCodeHighWord = (short) statusCodeHighWord;
+      }
+
+      /**
+       * Combines the {@link #statusCodeHighWord} with the provided <code>statusCodeLowWord</code> to produce the
+       * complete status code.
+       * 
+       * @param statusCodeLowWord the low word of the status code.
+       * @return
+       */
+      int createStatusCode(short statusCodeLowWord) {
+         int statusCode = (this.statusCodeHighWord << 16) | statusCodeLowWord;
+         return statusCode;
+      }
+
+      /**
+       * Predicate to determine if the {@link Type} member is {@link Type#InternalError}.
+       * 
+       * @return <code>true</code> when the member is {@link Type#InternalError}; otherwise, <code>false</code>.
+       */
+
+      boolean isInternalError() {
+         return this == InternalError;
+      }
+
+      /**
+       * Predicate to determine if the {@link Type} member is {@link Type#UserError}.
+       * 
+       * @return <code>true</code> when the member is {@link Type#UserError}; otherwise, <code>false</code>.
+       */
+
+      boolean isUserError() {
+         return this == UserError;
+      }
+   }
+
+   /**
+    * A lookup {@link Map} of {@link CatErrorCode} enumeration members by status code.
+    */
+
+   //@formatter:off
+   private static Map<Integer, CatErrorCode> statusCodeMap =
+      Arrays
+         .stream( CatErrorCode.values() )
+         .collect( Collectors.toMap( CatErrorCode::getStatusCode, Function.identity(), (f,s) -> f ) );
+
+   /**
+    * The sub-folder of the project's &quot;OSEE-INF&quot; folder that user support files are contained.
+    */
+
+   private static final String userSupportFolder = "user-support";
+
+   /**
+    * Gets the {@link CatErrorCode} enumeration member with the specified <code>statusCode</code>.
+    * 
+    * @param statusCode the status code of the {@link CatErrorCode} to get.
+    * @return {@link CatErrorCode} associated with the <code>statusCode</code>; otherwise, {@link CatErrorCode#InternalError}.
+    */
+
+   static CatErrorCode getByStatusCode(int statusCode) {
+      CatErrorCode catErrorCode = CatErrorCode.statusCodeMap.get(statusCode);
+      return Objects.nonNull(catErrorCode) ? catErrorCode : InternalError;
+   }
+
+   /**
+    * This method is invoked by the Plug-In activator to verify the {@link CatErrorCode} enumeration members as follows:
+    * <ul>
+    * <li>Verifies the status code is unique.</li>
+    * <li>Verifies a title string is set.</li>
+    * <li>Verifies the user support file can be accessed.</li>
+    */
+   
+   public static void verifyStatusCodes() {
+
+      Map<Integer, CatErrorCode> map = new HashMap<>();
+      LinkedList<IStatus> statusList = new LinkedList<>();
+      
+      for( CatErrorCode catErrorCode : CatErrorCode.values() ) {
+         
+         Integer statusCode = catErrorCode.getStatusCode();
+         CatErrorCode otherCatErrorCode = map.put(statusCode, catErrorCode);
+         
+         if( Objects.nonNull( otherCatErrorCode) ) {
+            //@formatter:off
+            CatPluginStatus duplicateErrorCodeStatus =
+                  new CatPluginStatus
+                         (
+                            CatErrorCode.InvalidCatErrorCode,
+                              "CatErrorCode enumeration members have the same status code."              + "\n"
+                            + "   Status Code:   " + Integer.toHexString( catErrorCode.getStatusCode() ) + "\n"
+                            + "   First Member:  " + otherCatErrorCode.name()                            + "\n"
+                            + "   Second Member: " + catErrorCode.name()                                 + "\n"
+                         );
+            //@formatter:on
+            statusList.add(duplicateErrorCodeStatus);
+         }
+
+         if (Objects.isNull(catErrorCode.title) || catErrorCode.title.isEmpty()) {
+            //@formatter:off
+            CatPluginStatus userErrorMustHaveTitleStatus =
+               new CatPluginStatus
+                      (
+                         CatErrorCode.InvalidCatErrorCode,
+                           "CatErrorCode enumeration member for a user error does not have a title." + "\n"
+                         + "   Member: " + catErrorCode.name() + "\n"
+                      );
+            //@formatter:on
+            statusList.add(userErrorMustHaveTitleStatus);
+         }
+
+         if (catErrorCode.isUserError()) {
+
+            if (Objects.isNull(catErrorCode.userSupportFile) || catErrorCode.userSupportFile.isEmpty()) {
+               //@formatter:off
+               CatPluginStatus userErrorMustHaveUrlStatus =
+                  new CatPluginStatus
+                         (
+                            CatErrorCode.InvalidCatErrorCode,
+                              "CatErrorCode enumeration member for a user error does not have a user support file." + "\n"
+                            + "   Member: " + catErrorCode.name() + "\n"
+                         );
+               //@formatter:on
+               statusList.add(userErrorMustHaveUrlStatus);
+            } else {
+
+               try {
+                  final String pathTail = CatErrorCode.userSupportFolder + "/" + catErrorCode.userSupportFile;
+                  final File file = OseeInf.getResourceAsFile(pathTail, CatErrorCode.class);
+                  if (!file.canRead()) {
+                     //@formatter:off
+                     CatPluginStatus userSupportFileErrorStatus =
+                        new CatPluginStatus
+                               (
+                                  CatErrorCode.InvalidCatErrorCode,
+                                  "Cannot read the user support file for a CatErrorCode." + "\n"
+                                  + "   Member: " + catErrorCode.name() + "\n"
+                               );
+                     //@formatter:on
+                     statusList.add(userSupportFileErrorStatus);
+                  }
+               } catch (Exception e) {
+                  //@formatter:off
+                  CatPluginStatus userSupportFileErrorStatus =
+                     new CatPluginStatus
+                            (
+                               CatErrorCode.InvalidCatErrorCode,
+                                 "Cannot read the user support file for a CatErrorCode." + "\n"
+                               + "   Member: " + catErrorCode.name() + "\n",
+                               e
+                            );
+                  //@formatter:on
+                  statusList.add(userSupportFileErrorStatus);
+               }
+            }
+         }
+      }
+
+      if (statusList.isEmpty()) {
+         return;
+      }
+
+      //@formatter:off
+      CatPluginException duplicateStatusCodeException =
+         new CatPluginException
+                (
+                   CatErrorCode.InvalidCatErrorCode,
+                   "CatErrorCode Exception",
+                   "Duplicate status codes were found for \"CatErrorCode\" enumeration memebers." + "\n",
+                   statusList.toArray(new IStatus[statusList.size()])
+                );
+      //@formatter:on
+      throw duplicateStatusCodeException;
+
+   }
+
+   /**
+    * Saves {@link IStatus} error severity level.
+    */
+
+   private final int severity;
+
+   /**
+    * Save a unique integer identifier for the error.
+    */
+
+   private final int statusCode;
+
+   /**
+    * Saves the {@link StatusManager} error dialog box style. See {@link StatusManager#BLOCK},
+    * {@link StatusManager#SHOW}, and {@link StatusManager#LOG}.
+    */
+
+   private final int style;
+
+   /**
+    * Save a title string used for all errors for the {@link CatErrorCode}.
+    */
+
+   private final String title;
+
+   /**
+    * Saves the {@link Type} of the error used to determine the {@link Control} for the user support area of
+    * {@link StatusManager} error dialogs.
+    */
+
+   private final Type type;
+
+   /**
+    * Saves the file name portion for the user support file. User support files are contained in the
+    * {@value CatErrorCode#userSupportFolder} sub-folder of the project's &quot;OSEE-INF&quot; folder.
+    */
+
+   private final String userSupportFile;
+
+   /**
+    * Creates a new {@link CatErrorCode} member.
+    * 
+    * @param type the {@link Type} of the error.
+    * @param style the {@link StatusManager} error dialog style.
+    * @param severity the {@link IStatus} error severity.
+    * @param statusCode the low word (short) of the status code. Must be unique for all {@link CatErrorCode} members
+    * with the same {@link #type}.
+    * @param title a title string used for all errors of the {@link CatErrorCode} kind.
+    * @param userSupportFile the file name portion of the user support file.
+    */
+
+   private CatErrorCode(Type type, int style, int severity, short typeStatusCode, String title, String userSupportFile) {
+      this.type = type;
+      this.style = style;
+      this.severity = severity;
+      this.statusCode = type.createStatusCode(typeStatusCode);
+      this.title = title;
+      this.userSupportFile = userSupportFile;
+   }
+
+   /**
+    * For {@link CatErrorCode.Type#UserError} errors, gets the URL of the error support file.
+    * 
+    * @return an {@link Optional} containing the {@link URL} of the error support file; otherwise, an empty
+    * {@link Optional}.
+    */
+
+   Optional<URL> getErrorSupportUrl() {
+      try {
+         final String pathTail = CatErrorCode.userSupportFolder + "/" + this.userSupportFile;
+         final URL url = OseeInf.getResourceAsUrl(pathTail, this.getClass());
+         return Optional.ofNullable(url);
+      } catch (Exception e) {
+         return Optional.empty();
+      }
+   }
+
+   /**
+    * Gets the {@link IStatus} severity of the error.
+    * 
+    * @return the error severity.
+    */
+
+   int getSeverity() {
+      return this.severity;
+   }
+
+   /**
+    * Get the unique status code for the error.
+    * 
+    * @return the error status code.
+    */
+
+   int getStatusCode() {
+      return this.statusCode;
+   }
+
+   /**
+    * Gets the {@link StatusManager} error dialog style to use for the error.
+    * 
+    * @return the error dialog style.
+    */
+
+   int getStyle() {
+      return this.style;
+   }
+
+   /**
+    * Gets a title string to be used at the start of the error message for errors of the {@link CatErrorCode} kind.
+    * 
+    * @return the title string.
+    */
+
+   String getTitle() {
+      return this.title;
+   }
+
+   /**
+    * Gets the {@link CatErrorCode.Type} of the error.
+    * 
+    * @return the error type.
+    */
+
+   Type getType() {
+      return this.type;
+   }
+
+   /**
+    * Predicate to determine if the error is a {@link CatErrorCode.Type#InternalError}.
+    * 
+    * @return <code>true</code> when the error is of the type {@link CatErrorCode.Type#InternalError}; otherwise,
+    * <code>false</code>.
+    */
+
+   boolean isInternalError() {
+      return this.getType().isInternalError();
+   }
+
+   /**
+    * Predicate to determine if the error is a {@link CatErrorCode.Type#UserError}.
+    * 
+    * @return <code>true</code> when the error is of the type {@link CatErrorCode.Type#UserError}; otherwise,
+    * <code>false</code>.
+    */
+
+   boolean isUserError() {
+      return this.getType().isUserError();
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/exception/CatErrorSupportProvider.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/exception/CatErrorSupportProvider.java
new file mode 100644
index 0000000..5f5fe30
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/exception/CatErrorSupportProvider.java
@@ -0,0 +1,178 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.exception;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.Objects;
+import java.util.Optional;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.dialogs.ErrorSupportProvider;
+import org.eclipse.ote.cat.plugin.composites.CatBrowserErrorSupport;
+import org.eclipse.ote.cat.plugin.composites.CatMessageErrorSupport;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.statushandlers.StatusManager;
+
+/**
+ * Creates a {@link Control} to display the CAT Plug-In user support area on {@link StatusManager} dialogs. An instance
+ * of this class is registered with the {@link StatusManager} by the CAT Plug-In activator.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class CatErrorSupportProvider extends ErrorSupportProvider {
+
+   /**
+    * Exception title string used for non-CAT Plug-In status and exceptions.
+    */
+
+   private static final String defaultExceptionTitle = "CAT Plug-In Internal Error";
+
+   /**
+    * Creates a {@link Control} to be displayed in the user support area of a {@link StatusManager} error dialog for
+    * non-{@link CatPluginException.CatPluginStatus} statuses.
+    * 
+    * @param parent the {@link Composite} of the {@link StatusManager} error dialog for the user support area.
+    * @param status the non-{@link CatPluginException.CatPluginStatus} to create a general support message for.
+    * @return a {@link Control} to be displayed in the user support area of an error dialog.
+    */
+
+   private static Control createGeneralSupportArea(Composite parent, IStatus status) {
+      String message = status.getMessage();
+      Throwable exception = status.getException();
+      String trace = null;
+      if (Objects.nonNull(exception)) {
+         String exceptionMessage = exception.getMessage();
+         //@formatter:off
+         message = new StringBuilder( message.length() + exceptionMessage.length() + 128 )
+                          .append( message ).append( "\n" )
+                          .append( "\n" )
+                          .append( exceptionMessage )
+                          .toString();
+         //@formatter:on
+         StringWriter stringWriter = new StringWriter();
+         PrintWriter printWriter = new PrintWriter(stringWriter);
+         exception.printStackTrace(printWriter);
+         trace = stringWriter.toString();
+      }
+      return new CatMessageErrorSupport(parent, CatErrorSupportProvider.defaultExceptionTitle, message, trace);
+   }
+
+   /**
+    * Creates a {@link Control} to be displayed in the user support area of a {@link StatusManager} error dialog
+    * according to the <code>status</code> class as follows:
+    * <dl style="margin-left:2em;">
+    * <dt>When <code>status</code> is an instance of {@link CatPluginException.CatPluginStatus}:</dt>
+    * <dd>A {@link CatBrowserErrorSupport} {@link Control} is created for {@link CatErrorCode.Type#UserError}s and a
+    * {@link CatMessageErrorSupport} {@link Control} is created for {@link CatErrorCode.Type#InternalError}s.</dd>
+    * <dt>When <code>status</code> is not an instance of {@link CatPluginException.CatPluginStatus}:</dt>
+    * <dd>If the exception chain for the <code>status</code> contains a {@link CatPluginException} a {@link Control} is
+    * created for the {@link CatPluginException.CatPluginStatus} contained in the exception chain as described above.
+    * Otherwise, the method {@link #createGeneralSupportArea} is used to create the {@link Control}.</dd>
+    * </dl>
+    * 
+    * @param parent the {@link Composite} of the {@link StatusManager} error dialog for the user support area.
+    * @param status the {@link IStatus} to create user support area {@link Control} for.
+    * @return a {@link Control} to be displayed in the user support area of an error dialog.
+    */
+
+   private static Control createSupportAreaControl(Composite parent, IStatus status) {
+
+      if (!(status instanceof CatPluginStatus)) {
+
+         Optional<CatPluginException> catPluginExceptionOptional =
+            CatErrorSupportProvider.getCatPluginException(status);
+
+         if (!catPluginExceptionOptional.isPresent()) {
+            return CatErrorSupportProvider.createGeneralSupportArea(parent, status);
+         }
+
+         CatPluginException catPluginException = catPluginExceptionOptional.get();
+         status = catPluginException.getStatus();
+      }
+
+      CatPluginStatus catPluginStatus = (CatPluginStatus) status;
+      CatErrorCode catErrorCode = catPluginStatus.getCatErrorCode();
+      CatErrorCode.Type catErrorType = catErrorCode.getType();
+
+      switch (catErrorType) {
+
+         case UserError:
+            Optional<URL> urlOptional = catErrorCode.getErrorSupportUrl();
+            if (urlOptional.isPresent()) {
+               return new CatBrowserErrorSupport(parent, urlOptional.get());
+            }
+            //case fall through to create an internal error control.
+
+         case InternalError:
+            CatPluginException catPluginException = (CatPluginException) catPluginStatus.getException();
+            String title = catPluginException.getTitle();
+            StringBuilder stringBuilder = new StringBuilder(1024);
+            stringBuilder.append(status.getMessage()).append("\n\n");
+            IStatus[] children = status.getChildren();
+            for (IStatus child : children) {
+               stringBuilder.append(child.getMessage()).append("\n\n");
+            }
+            String message = stringBuilder.toString();
+            String trace = catPluginException.getTrace();
+            return new CatMessageErrorSupport(parent, title, message, trace);
+      }
+
+      return CatErrorSupportProvider.createGeneralSupportArea(parent, catPluginStatus);
+
+   }
+
+   /**
+    * Looks for and extracts the {@link CatPluginException} associated with the {@link IStatus}. When the
+    * {@link IStatus} contains a {@link CatPluginException} it is returned. When the {@link IStatus} contains an
+    * exception that is not a {@link CatPluginException}, the first {@link CatPluginException} in the exception cause
+    * chain is returned.
+    * 
+    * @param status the {@link IStatus} to extract a {@link CatPluginException} from.
+    * @return an {@link Optional} containing a {@link CatPluginException} when one is found; otherwise, an empty
+    * {@link Optional}.
+    */
+
+   private static Optional<CatPluginException> getCatPluginException(IStatus status) {
+      if (status instanceof CatPluginStatus) {
+         return Optional.of((CatPluginException) status.getException());
+      }
+      for (Throwable exception = status.getException(); Objects.nonNull(exception); exception = exception.getCause()) {
+         if (exception instanceof CatPluginException) {
+            return Optional.of((CatPluginException) exception);
+         }
+      }
+      return Optional.empty();
+   }
+
+   /**
+    * Creates the {@link ErrorSupportProvider} for the CAT Plug-In.
+    */
+
+   public CatErrorSupportProvider() {
+   }
+
+   /**
+    * Creates the {@link Control} for the user support area on {@link StatusManager} dialogs for the CAT Plug-In.
+    * <p>
+    * {@inheritDoc}
+    */
+
+   @Override
+   public Control createSupportArea(Composite parent, IStatus status) {
+      return CatErrorSupportProvider.createSupportAreaControl(parent, status);
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/exception/CatPluginException.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/exception/CatPluginException.java
new file mode 100644
index 0000000..c3f9559
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/exception/CatPluginException.java
@@ -0,0 +1,268 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.exception;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Objects;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.ui.statushandlers.IStatusAdapterConstants;
+import org.eclipse.ui.statushandlers.StatusAdapter;
+import org.eclipse.ui.statushandlers.StatusManager;
+
+/**
+ * The {@link RuntimeException} class CAT Plug-In exceptions are derived from.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class CatPluginException extends RuntimeException {
+
+   /**
+    * The introduction used for internal exception messages.
+    */
+
+   //@formatter:off
+   private static final String internalErrorMessageHeader =
+      "Please forward the error details to your support team.\n\n";
+   //@formatter:on
+
+   /**
+    * For internal error type, prepends the exception message with an introduction requesting the user to contact the
+    * support team.
+    * 
+    * @param catErrorCode the exception {@link CatErrorCode} used to determine if the error type is
+    * {@link CatErrorCode.Type#InternalError}.
+    * @param message the exception message.
+    * @return the exception message with introduction.
+    */
+
+   static String buildMessage(CatErrorCode catErrorCode, String message) {
+
+      String safeMessage = Objects.nonNull(message) ? message : CatPluginException.undescribedMessage;
+
+      if (catErrorCode.isInternalError()) {
+         //@formatter:off
+         return
+            new StringBuilder( message.length() + internalErrorMessageHeader.length() )
+                   .append( internalErrorMessageHeader )
+                   .append( message )
+                   .toString();
+         //@formatter:on
+      }
+
+      return safeMessage;
+   }
+
+   /**
+    * The error dialog title to use when a title was not provided for the {@link CatPluginException}.
+    */
+
+   private static final String defaultTitle = "CAT Plug-in Error";
+
+   /**
+    * A sentinel value used to indicate that a cause exception is not available.
+    */
+
+   public static final Throwable noCause = null;
+
+   /**
+    * The serialization version identifier;
+    */
+
+   private static final long serialVersionUID = 0L;
+
+   /**
+    * The exception message used with a message was not provided for the {@link CatPluginException}.
+    */
+
+   private static final String undescribedMessage = "Undescribed Exception";
+
+   /**
+    * Gets a non-<code>null</code> title.
+    * 
+    * @param unsafeTitle the title provided to the {@link CatPluginException} which may be <code>null</code>.
+    * @return <code>unsafeTitle</code> when <code>unsafeTitle</code> is non-<code>null</code>; otherwise,
+    * {@value #defaultTitle}.
+    */
+
+   private static String getSafeTitle(String unsafeTitle) {
+      return Objects.nonNull(unsafeTitle) ? unsafeTitle : CatPluginException.defaultTitle;
+   }
+
+   /**
+    * Saves the {@link IStatus} implementation created from the constructor parameters. This is the {@link IStatus}
+    * object passed to the {@link StatusManager} by the {@link #log} method.
+    */
+
+   private final CatPluginStatus status;
+
+   /**
+    * Saves the {@link StatusManager} error dialog style bits. See {@link StatusManager#BLOCK},
+    * {@link StatusManager#SHOW}, and {@link StatusManager@LOG}.
+    */
+
+   private int style;
+
+   /**
+    * Saves the title to be used for the error dialog.
+    */
+
+   private final String title;
+
+   /**
+    * Creates a new runtime {@link CatPluginException}.
+    * 
+    * @param catErrorCode the {@link CatErrorCode} providing a unique reference code for the error, the
+    * {@link StatusManager} display hints, and the {@link IStatus} severity.
+    * @param message a detailed message describing the exception. When <code>null</code> the message
+    * {@value #undescribedMessage} will be used.
+    */
+
+   public CatPluginException(CatErrorCode catErrorCode, String message) {
+      super(CatPluginException.buildMessage(catErrorCode, message));
+
+      this.status = new CatPluginStatus(catErrorCode, (IStatus) null, this);
+      this.style = catErrorCode.getStyle();
+      this.title = CatPluginException.getSafeTitle(catErrorCode.getTitle());
+   }
+
+   /**
+    * Creates a new runtime {@link CatPluginException}. The <code>cause</code> {@link IStatus} object will be linked as
+    * the child for the {@link IStatus} created for this exception.
+    * 
+    * @param catErrorCode the {@link CatErrorCode} providing a unique reference code for the error, the
+    * {@link StatusManager} display hints, and the {@link IStatus} severity.
+    * @param message a detailed message describing the exception. When <code>null</code> the message
+    * {@value #undescribedMessage} will be used.
+    * @param cause the {@link IStatus} the exception is being built for. This parameter may be <code>null</code>.
+    */
+
+   public CatPluginException(CatErrorCode catErrorCode, String message, IStatus cause) {
+      super(CatPluginException.buildMessage(catErrorCode, message));
+
+      this.status = new CatPluginStatus(catErrorCode, cause, this);
+      this.style = catErrorCode.getStyle();
+      this.title = CatPluginException.getSafeTitle(catErrorCode.getTitle());
+   }
+
+   /**
+    * Creates a new runtime {@link CatPluginException}. The <code>cause</code> {@link IStatus} object will be linked as
+    * the child for the {@link IStatus} created for this exception.
+    * 
+    * @param catErrorCode the {@link CatErrorCode} providing a unique reference code for the error, the
+    * {@link StatusManager} display hints, and the {@link IStatus} severity.
+    * @param message a detailed message describing the exception. When <code>null</code> the message
+    * {@value #undescribedMessage} will be used.
+    * @param cause an array of {@link IStatus} objects the exception is being built for. This parameter may be
+    * <code>null</code>.
+    */
+
+   public CatPluginException(CatErrorCode catErrorCode, String title, String message, IStatus[] cause) {
+      super(CatPluginException.buildMessage(catErrorCode, message));
+
+      this.status = new CatPluginStatus(catErrorCode, cause, this);
+      this.style = catErrorCode.getStyle();
+      this.title = CatPluginException.getSafeTitle(catErrorCode.getTitle());
+   }
+
+   /**
+    * Creates a new runtime {@link CatPluginException}. When the <code>cause</code> exception is either a
+    * {@link CatPluginException} or a {@link CoreException}, the {@link IStatus} object from the <code>cause</code>
+    * exception will be linked as the child for the {@link IStatus} created for this exception.
+    * 
+    * @param catErrorCode the {@link CatErrorCode} providing a unique reference code for the error, the
+    * {@link StatusManager} display hints, and the {@link IStatus} severity.
+    * @param message a detailed message describing the exception. When <code>null</code> the message
+    * {@value #undescribedMessage} will be used.
+    * @param cause the causing exception. This parameter may be <code>null</code>.
+    */
+
+   public CatPluginException(CatErrorCode catErrorCode, String message, Throwable cause) {
+      super(CatPluginException.buildMessage(catErrorCode, message));
+      if (Objects.nonNull(cause)) {
+         this.initCause(cause);
+      }
+      //@formatter:off
+      IStatus childStatus =
+         Objects.isNull(cause)
+            ? null
+            : (cause instanceof CatPluginException)
+                 ? ((CatPluginException) cause).status
+                 : (cause instanceof CoreException)
+                      ? ((CoreException) cause).getStatus()
+                      : null;
+      //@formatter:on
+      this.status = new CatPluginStatus(catErrorCode, childStatus, this);
+      this.style = catErrorCode.getStyle();
+      this.title = CatPluginException.getSafeTitle(catErrorCode.getTitle());
+   }
+
+   /**
+    * Gets the {@link CatErrorCode} associated with the exception's {@link CatPluginStatus}.
+    * 
+    * @return the {@link CatErrorCode} for the exception.
+    */
+
+   public CatErrorCode getCatErrorCode() {
+      return this.status.getCatErrorCode();
+   }
+
+   /**
+    * Gets the {@link CatPluginStatus} the {@link CatPluginException} was created for or the {@link CatPluginStatus}
+    * created from the {@link CatPluginException}.
+    * 
+    * @return the {@link CatPluginStatus} associated with the exception.
+    */
+
+   public CatPluginStatus getStatus() {
+      return this.status;
+   }
+
+   /**
+    * Gets the exception title.
+    * 
+    * @return title string.
+    */
+
+   public String getTitle() {
+      return this.title;
+   }
+
+   /**
+    * Generates a stack trace string for the exception.
+    * 
+    * @return the exception trace.
+    */
+
+   public String getTrace() {
+      StringWriter stringWriter = new StringWriter();
+      PrintWriter printWriter = new PrintWriter(stringWriter);
+      this.printStackTrace(printWriter);
+      return stringWriter.toString();
+   }
+
+   /**
+    * Logs the {@link #status} with the {@link StatusManager}.
+    */
+
+   public void log() {
+
+      StatusAdapter statusAdapter = new StatusAdapter(this.status);
+      statusAdapter.setProperty(IStatusAdapterConstants.TITLE_PROPERTY, this.title);
+      statusAdapter.setProperty(IStatusAdapterConstants.TIMESTAMP_PROPERTY, System.currentTimeMillis());
+      StatusManager.getManager().handle(statusAdapter, this.style);
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/exception/CatPluginStatus.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/exception/CatPluginStatus.java
new file mode 100644
index 0000000..a55090a
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/exception/CatPluginStatus.java
@@ -0,0 +1,211 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.exception;
+
+import java.util.Objects;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.ote.cat.plugin.CatPlugin;
+import org.eclipse.ui.statushandlers.StatusManager;
+
+/**
+ * The {@link IStatus} implementation class used when reporting the {@link CatPluginException} to the
+ * {@link StatusManager}.
+ */
+
+class CatPluginStatus extends Status {
+
+   /**
+    * Save the linked children {@link IStatus} objects. <code>null</code> is a sentinel value to indicate that this
+    * status does not have a child.
+    */
+
+   private final IStatus[] children;
+
+   /**
+    * Creates a new {@link CatPluginStatus}.
+    *
+    * @param catErrorCode the {@link CatErrorCode} providing a unique reference code for the error, the
+    * {@link StatusManager} display hints, and the {@link IStatus} severity.
+    * @param message a detailed message describing the exception. When <code>null</code> the message
+    * {@value #undescribedMessage} will be used.
+    */
+
+   public CatPluginStatus(CatErrorCode catErrorCode, String message) {
+      super(catErrorCode.getSeverity(), CatPlugin.getIdentifier(), catErrorCode.getStatusCode(),
+         CatPluginException.buildMessage(catErrorCode, message), null);
+      this.children = null;
+   }
+
+   /**
+    * Creates a new {@link CatPluginStatus}.
+    *
+    * @param catErrorCode the {@link CatErrorCode} providing a unique reference code for the error, the
+    * {@link StatusManager} display hints, and the {@link IStatus} severity.
+    * @param message a detailed message describing the exception. When <code>null</code> the message
+    * {@value #undescribedMessage} will be used.
+    * @param exception the exception this {@link IStatus} is being created for.
+    */
+
+   public CatPluginStatus(CatErrorCode catErrorCode, String message, Exception e) {
+      super(catErrorCode.getSeverity(), CatPlugin.getIdentifier(), catErrorCode.getStatusCode(),
+         CatPluginException.buildMessage(catErrorCode, message), e);
+      this.children = null;
+   }
+
+   /**
+    * Creates a new {@link CatPluginStatus}.
+    *
+    * @param catErrorCode the {@link CatErrorCode} providing a unique reference code for the error, the
+    * {@link StatusManager} display hints, and the {@link IStatus} severity.
+    * @param message a detailed message describing the exception. When <code>null</code> the message
+    * {@value #undescribedMessage} will be used.
+    * @param childStatus the {@link IStatus} to attach as a child. This parameter may be <code>null</code>.
+    */
+
+   public CatPluginStatus(CatErrorCode catErrorCode, String message, IStatus childStatus) {
+      super(catErrorCode.getSeverity(), CatPlugin.getIdentifier(), catErrorCode.getStatusCode(),
+         CatPluginException.buildMessage(catErrorCode, message), null);
+      //@formatter:off
+         this.children =
+            Objects.nonNull(childStatus)
+               ? new IStatus[] { childStatus }
+               : null;
+      //@formatter:on
+      if (Objects.nonNull(this.children)) {
+         int maxSeverity = catErrorCode.getSeverity();
+         for (IStatus child : this.children) {
+            maxSeverity = Math.max(maxSeverity, child.getSeverity());
+         }
+         this.setSeverity(maxSeverity);
+      }
+   }
+
+   /**
+    * Creates a new {@link CatPluginStatus}.
+    *
+    * @param catErrorCode the {@link CatErrorCode} providing a unique reference code for the error, the
+    * {@link StatusManager} display hints, and the {@link IStatus} severity.
+    * @param childStatus the {@link IStatus} to attach as a child. This parameter may be <code>null</code>.
+    * @param exception the exception this {@link IStatus} is being created for.
+    */
+
+   public CatPluginStatus(CatErrorCode catErrorCode, IStatus childStatus, Exception exception) {
+      super(catErrorCode.getSeverity(), CatPlugin.getIdentifier(), catErrorCode.getStatusCode(), exception.getMessage(),
+         exception);
+      //@formatter:off
+         this.children =
+            Objects.nonNull(childStatus)
+               ? new IStatus[] { childStatus }
+               : null;
+      //@formatter:on
+      if (Objects.nonNull(this.children)) {
+         int maxSeverity = catErrorCode.getSeverity();
+         for (IStatus child : this.children) {
+            maxSeverity = Math.max(maxSeverity, child.getSeverity());
+         }
+         this.setSeverity(maxSeverity);
+      }
+   }
+
+   /**
+    * Creates a new {@link CatPluginStatus}.
+    *
+    * @param catErrorCode the {@link CatErrorCode} providing a unique reference code for the error, the
+    * {@link StatusManager} display hints, and the {@link IStatus} severity.
+    * @param message a detailed message describing the exception. When <code>null</code> the message
+    * {@value #undescribedMessage} will be used.
+    * @param children an array of child {@link IStatus} objects to attach as a children. This parameter may be
+    * <code>null</code>.
+    */
+
+   public CatPluginStatus(CatErrorCode catErrorCode, String message, IStatus[] children) {
+      super(catErrorCode.getSeverity(), CatPlugin.getIdentifier(), catErrorCode.getStatusCode(),
+         CatPluginException.buildMessage(catErrorCode, message), null);
+      //@formatter:off
+         this.children =
+            Objects.nonNull(children)
+               ? children
+               : null;
+      //@formatter:on
+      if (Objects.nonNull(this.children)) {
+         int maxSeverity = catErrorCode.getSeverity();
+         for (IStatus child : this.children) {
+            maxSeverity = Math.max(maxSeverity, child.getSeverity());
+         }
+         this.setSeverity(maxSeverity);
+      }
+
+   }
+
+   /**
+    * Creates a new {@link CatPluginStatus}.
+    *
+    * @param catErrorCode the {@link CatErrorCode} providing a unique reference code for the error, the
+    * {@link StatusManager} display hints, and the {@link IStatus} severity.
+    * @param children an array of child {@link IStatus} objects to attach as a children. This parameter may be
+    * <code>null</code>.
+    * @param excetion the exception this {@link IStatus} is being created for.
+    */
+
+   public CatPluginStatus(CatErrorCode catErrorCode, IStatus[] children, Exception exception) {
+      super(catErrorCode.getSeverity(), CatPlugin.getIdentifier(), catErrorCode.getStatusCode(), exception.getMessage(),
+         exception);
+      //@formatter:off
+         this.children =
+            Objects.nonNull(children)
+               ? children
+               : null;
+      //@formatter:on
+      if (Objects.nonNull(this.children)) {
+         int maxSeverity = catErrorCode.getSeverity();
+         for (IStatus child : this.children) {
+            maxSeverity = Math.max(maxSeverity, child.getSeverity());
+         }
+         this.setSeverity(maxSeverity);
+      }
+   }
+
+   /**
+    * Gets the {@link IStatus} code as a {@link CatErrorCode}.
+    * 
+    * @return the {@link CatErrorCode} associated with the {@link IStatus} code.
+    */
+
+   public CatErrorCode getCatErrorCode() {
+      int code = this.getCode();
+      CatErrorCode catErrorCode = CatErrorCode.getByStatusCode(code);
+      return catErrorCode;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+
+   @Override
+   public IStatus[] getChildren() {
+      if (Objects.nonNull(this.children)) {
+         return this.children;
+      }
+      return new IStatus[0];
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+
+   @Override
+   public boolean isMultiStatus() {
+      return Objects.nonNull(this.children);
+   }
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/DirectoryAutoStore.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/DirectoryAutoStore.java
deleted file mode 100644
index 62e5a59..0000000
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/DirectoryAutoStore.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*********************************************************************
- * Copyright (c) 2024 Boeing
- * 
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * https://www.eclipse.org/legal/epl-2.0/
- * 
- * SPDX-License-Identifier: EPL-2.0
- * 
- * Contributors: Boeing - initial API and implementation
- **********************************************************************/
-
-package org.eclipse.ote.cat.plugin.fieldeditors;
-
-import org.eclipse.jface.preference.DirectoryFieldEditor;
-import org.eclipse.jface.preference.StringFieldEditor;
-import org.eclipse.swt.widgets.Composite;
-
-/**
- * An extension of the {@link DirectoryFieldEditor} that immediately saves the selected directory in the preference
- * store upon change to make the value available to other field editors.
- * 
- * @author Loren K. Ashley
- */
-
-public class DirectoryAutoStore extends DirectoryFieldEditor {
-
-   /**
-    * Creates a directory field editor that immediately saves the selected directory in the preference store.
-    * 
-    * @param preferenceStoreName the name used to access the preference value in the preference store.
-    * @param fieldEditorLabel the label for the field editor.
-    * @param parentComposite the {@link Composite} to attach this field editor to.
-    */
-
-   public DirectoryAutoStore(String preferenceStoreName, String fieldEditorLabel, Composite parentComposite) {
-      super(preferenceStoreName, fieldEditorLabel, parentComposite);
-      this.setValidateStrategy(StringFieldEditor.VALIDATE_ON_FOCUS_LOST);
-   }
-
-   /**
-    * Saves the selected directory in the preference store.
-    * <p>
-    * {@inheritDoc}
-    */
-
-   protected void valueChanged() {
-      this.doStore();
-   }
-}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/DirectoryAutoStoreFieldEditor.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/DirectoryAutoStoreFieldEditor.java
new file mode 100644
index 0000000..129cb30
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/DirectoryAutoStoreFieldEditor.java
@@ -0,0 +1,103 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.fieldeditors;
+
+import java.io.File;
+import java.util.Objects;
+import org.eclipse.jface.preference.DirectoryFieldEditor;
+import org.eclipse.jface.preference.StringFieldEditor;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * An extension of the {@link DirectoryFieldEditor} that immediately saves the selected directory in the preference
+ * store upon change to make the value available to other field editors.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class DirectoryAutoStoreFieldEditor extends DirectoryFieldEditor {
+
+   /**
+    * Error message template for the message displayed at the top of the preference page when a directory is not
+    * selected.
+    */
+
+   private static final String directoryNotSelectedMessage = "%s A directory must be selected.";
+
+   /**
+    * Error message template for the message displayed at the top of the preference page when the selected file is not a
+    * regular file.
+    */
+
+   private static final String notADirectoryMessage = "%s Selection must be an existing directory.";
+
+   /**
+    * Creates a directory field editor that immediately saves the selected directory in the preference store.
+    * 
+    * @param preferenceStoreName the name used to access the preference value in the preference store.
+    * @param fieldEditorLabel the label for the field editor.
+    * @param parentComposite the {@link Composite} to attach this field editor to.
+    */
+
+   public DirectoryAutoStoreFieldEditor(String preferenceStoreName, String fieldEditorLabel, Composite parentComposite) {
+      super(preferenceStoreName, fieldEditorLabel, parentComposite);
+      this.setValidateStrategy(StringFieldEditor.VALIDATE_ON_FOCUS_LOST);
+      this.setPropertyChangeListener(this::propertyChanged);
+   }
+
+   /**
+    * Saves the selected directory in the preference store.
+    * <p>
+    * {@inheritDoc}
+    */
+
+   protected void propertyChanged(PropertyChangeEvent event) {
+      this.doStore();
+   }
+
+   /**
+    * {@inheritDoc}
+    * <p>
+    * Verifies:
+    * <ul>
+    * <li>a selection has been made, and</li>
+    * <li>the selection references an existing directory.</li>
+    * </ul>
+    */
+
+   @Override
+   protected boolean checkState() {
+
+      String text = this.getTextControl().getText();
+      String path = Objects.nonNull(text) ? text : null;
+
+      if (Objects.isNull(path) || path.isEmpty()) {
+         String message = String.format(DirectoryAutoStoreFieldEditor.directoryNotSelectedMessage, this.getLabelText());
+         this.showErrorMessage(message);
+         return false;
+      }
+
+      File file = new File(path);
+
+      if (!file.isDirectory()) {
+         String message = String.format(DirectoryAutoStoreFieldEditor.notADirectoryMessage, this.getLabelText());
+         this.showErrorMessage(message);
+         return false;
+      }
+
+      this.clearErrorMessage();
+      return true;
+
+   }
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/PleConfigurationLoader.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/PleConfigurationLoaderFieldEditor.java
similarity index 82%
rename from org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/PleConfigurationLoader.java
rename to org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/PleConfigurationLoaderFieldEditor.java
index 4faf84b..5d28a8a 100644
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/PleConfigurationLoader.java
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/PleConfigurationLoaderFieldEditor.java
@@ -24,7 +24,7 @@
  * @author Loren K. Ashley
  */
 
-public class PleConfigurationLoader extends StringButtonFieldEditor {
+public class PleConfigurationLoaderFieldEditor extends StringButtonFieldEditor {
 
    /**
     * Label for the {@link StringButtonFieldEditor} button.
@@ -33,18 +33,18 @@
    private static String buttonText = "Load";
 
    /**
-    * Creates a new {@link PleConfigurationLoader} {@link FieldEditor}.
+    * Creates a new {@link PleConfigurationLoaderFieldEditor} {@link FieldEditor}.
     * 
     * @param preferenceStoreName the name used to access the preference value in the preference store.
     * @param fieldEditorLabel the label for the field editor.
     * @param parentComposite the {@link Composite} to attach this field editor to.
     */
 
-   public PleConfigurationLoader(String preferenceStoreName, String fieldEditorLabel, Composite parentComposite) {
+   public PleConfigurationLoaderFieldEditor(String preferenceStoreName, String fieldEditorLabel, Composite parentComposite) {
       super(preferenceStoreName, fieldEditorLabel, parentComposite);
       final Text text = this.getTextControl();
       text.setEditable(false);
-      this.setChangeButtonText(PleConfigurationLoader.buttonText);
+      this.setChangeButtonText(PleConfigurationLoaderFieldEditor.buttonText);
    }
 
    /**
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/Project.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/ProjectFieldEditor.java
similarity index 72%
rename from org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/Project.java
rename to org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/ProjectFieldEditor.java
index 7c0e511..0121d59 100644
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/Project.java
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/ProjectFieldEditor.java
@@ -16,17 +16,20 @@
 
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.stream.Collectors;
 import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IWorkspace;
-import org.eclipse.core.resources.IWorkspaceRoot;
-import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.jface.preference.FieldEditor;
 import org.eclipse.jface.viewers.ArrayContentProvider;
 import org.eclipse.jface.window.Window;
+import org.eclipse.ote.cat.plugin.CatPlugin;
+import org.eclipse.ote.cat.plugin.composites.AddRemoveBox;
 import org.eclipse.ote.cat.plugin.composites.ButtonBox;
-import org.eclipse.ote.cat.plugin.composites.SelectionBox;
+import org.eclipse.ote.cat.plugin.preferencepage.Preference;
+import org.eclipse.ote.cat.plugin.util.Projects;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.widgets.Composite;
@@ -40,22 +43,16 @@
  * (CAT).
  */
 
-public class Project extends FieldEditor {
+public class ProjectFieldEditor extends FieldEditor {
 
    /**
-    * The preferred minimum number of grid columns for the control.
+    * An extension of the class {@link AddRemoveBox} for selection of Eclipse Projects ({@link IProject}) objects.
     */
 
-   private static final int controlColumnCount = 2;
-
-   /**
-    * An extension of the class {@link SelectionBox} for selection of Eclipse Projects ({@link IProject}) objects.
-    */
-
-   public class ProjectSelectionBox extends SelectionBox<IProject> {
+   public class ProjectSelectionBox extends AddRemoveBox<IProject> {
 
       /**
-       * Creates a new {@link SelectionBox} for {@link IProject} objects.
+       * Creates a new {@link AddRemoveBox} for {@link IProject} objects.
        * 
        * @param parent the {@link Composite} the selection box will be attached to.
        * @param selectionChangedAction a callback method for when the selection box selection has changed.
@@ -65,10 +62,16 @@
          super(parent, selectionChangedAction);
       }
 
-   };
+   }
 
    /**
-    * Saves the control for the selected projects box.
+    * The preferred minimum number of grid columns for the control.
+    */
+
+   private static final int controlColumnCount = 2;
+
+   /**
+    * Saves the control for the "add" and "remove" buttons.
     */
 
    private ButtonBox buttonBox;
@@ -80,7 +83,7 @@
    private Composite parent;
 
    /**
-    * Saves the control for the project select box.
+    * Saves the control for the selected projects table view box.
     */
 
    private ProjectSelectionBox projectSelectionBox;
@@ -93,7 +96,7 @@
     * @param parent the parent of the field editor's control
     */
 
-   public Project(String name, String labelText, Composite parent) {
+   public ProjectFieldEditor(String name, String labelText, Composite parent) {
       this.parent = parent;
       this.init(name, labelText);
       this.createControl(parent);
@@ -108,15 +111,12 @@
     */
 
    private void addPressed() {
-
-      List<IProject> projects = this.getProjectSelections();
-
-      if (Objects.nonNull(projects) && (projects.size() > 0)) {
-         this.getProjectSelectionBox().add(projects);
-         this.selectionChanged();
-         this.setPresentsDefaultValue(false);
+      List<IProject> newProjects = this.promptForNewProjects();
+      if (Objects.nonNull(newProjects) && (newProjects.size() > 0)) {
+         this.getProjectSelectionBox().add(newProjects);
       }
-
+      this.selectionChanged();
+      this.setPresentsDefaultValue(false);
    }
 
    /**
@@ -135,7 +135,6 @@
 
    @Override
    protected void adjustForNumColumns(int numColumns) {
-
       {
          /*
           * Use the first full grid row for the label
@@ -144,7 +143,6 @@
          GridData labelGridData = (GridData) labelControl.getLayoutData();
          labelGridData.horizontalSpan = numColumns;
       }
-
       {
          /*
           * Use all but the last grid column of the second row for the table
@@ -153,7 +151,6 @@
          GridData tableGridData = (GridData) tableControl.getLayoutData();
          tableGridData.horizontalSpan = Math.max(numColumns - 1, 1);
       }
-
       {
          /*
           * Use the last column of the second row for the buttons
@@ -176,7 +173,6 @@
     */
 
    private int createButtonBoxEnableMask() {
-
       //@formatter:off
       int mask =
            ButtonBox.AddButton
@@ -186,51 +182,53 @@
    }
 
    /**
-    * Creates and lays out the controls for the {@link Project} {@link FieldEditor}.
+    * Creates and lays out the controls for the {@link ProjectFieldEditor} {@link FieldEditor}.
     * <p>
     * {@inheritDoc}
     */
 
    @Override
    protected void doFillIntoGrid(Composite parent, int numColumns) {
-
       {
          final GridData labelGridData = new GridData();
          Control labelControl = this.getLabelControl(parent);
          labelControl.setLayoutData(labelGridData);
       }
-
       {
          final GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
          Control tableControl = this.getProjectSelectionBox();
          tableControl.setLayoutData(tableGridData);
       }
-
       {
          final GridData buttonBoxGridData = new GridData();
          Control buttonBoxControl = this.getButtonBox();
          buttonBoxControl.setLayoutData(buttonBoxGridData);
       }
-
       this.adjustForNumColumns(numColumns);
       this.getButtonBox().enableButtons();
    }
 
    /**
-    * Gets the stored preference string of indicating which projects have been selected. The named projects are found
-    * and loaded in to the {@link #projectSelectionBox}. {@inheritDoc}
+    * Gets the stored preference string indicating which projects have been selected. The named projects are found and
+    * loaded in to the {@link #projectSelectionBox}.
+    * <p>
+    * {@inheritDoc}
     */
 
    @Override
    protected void doLoad() {
-      //TODO:
-      //if (list != null) {
-      //   String s = getPreferenceStore().getString(getPreferenceName());
-      //   String[] array = parseString(s);
-      //   for (String element : array) {
-      //      list.add(element);
-      //   }
-      //}
+      String jtsProjectsPreferenceString = Preference.JTS_PROJECTS.get();
+      String[] projectStrings = jtsProjectsPreferenceString.split(",");
+      Map<String, IProject> projectMap = Projects.getProjectMap(IProject::toString);
+      List<IProject> projects = new LinkedList<>();
+      for (String projectString : projectStrings) {
+         IProject project = projectMap.get(projectString);
+         if (project == null) {
+            continue;
+         }
+         projects.add(project);
+      }
+      this.getProjectSelectionBox().add(projects);
    }
 
    /**
@@ -252,11 +250,16 @@
 
    @Override
    protected void doStore() {
-      //TODO:
-      //String s = createList(list.getItems());
-      //if (s != null) {
-      //   getPreferenceStore().setValue(getPreferenceName(), s);
-      //}
+      //@formatter:off
+      String jtsProjectsPreferenceString =
+         this
+            .getProjectSelectionBox()
+            .getContents(LinkedList::new)
+            .stream()
+            .map( IProject::toString )
+            .collect( Collectors.joining(",") );
+      //@formatter:on
+      Preference.JTS_PROJECTS.set(jtsProjectsPreferenceString);
    }
 
    /**
@@ -266,7 +269,6 @@
     */
 
    private ButtonBox getButtonBox() {
-
       return //
       Objects.nonNull(this.buttonBox) //
          ? this.buttonBox //
@@ -279,12 +281,12 @@
     * <p>
     * {@inheritDoc}
     * 
-    * @return {@value Project#controlColumnCount}
+    * @return {@value ProjectFieldEditor#controlColumnCount}
     */
 
    @Override
    public int getNumberOfControls() {
-      return Project.controlColumnCount;
+      return ProjectFieldEditor.controlColumnCount;
    }
 
    /**
@@ -294,12 +296,13 @@
     */
 
    private ProjectSelectionBox getProjectSelectionBox() {
-
-      return //
-      Objects.nonNull(this.projectSelectionBox) //
-         ? this.projectSelectionBox //
-         : (this.projectSelectionBox = new ProjectSelectionBox(this.parent, this::selectionChanged));
-
+      if (Objects.nonNull(this.projectSelectionBox)) {
+         return this.projectSelectionBox;
+      }
+      this.projectSelectionBox = new ProjectSelectionBox(this.parent, this::selectionChanged);
+      List<IProject> projectsWithNature = Projects.getProjectsWithNature(CatPlugin.getCatNatureIdentifier());
+      this.projectSelectionBox.add(projectsWithNature);
+      return this.projectSelectionBox;
    }
 
    /**
@@ -308,13 +311,8 @@
     * @return a list of the projects selected by the user.
     */
 
-   protected List<IProject> getProjectSelections() {
-
-      IWorkspace workspace = ResourcesPlugin.getWorkspace();
-      IWorkspaceRoot workSpaceRoot = workspace.getRoot();
-      IProject[] projectArray = workSpaceRoot.getProjects();
-      List<IProject> projectList = Arrays.asList(projectArray);
-
+   protected List<IProject> promptForNewProjects() {
+      List<IProject> projectsWithoutNature = Projects.getProjectsWithoutNature(CatPlugin.getCatNatureIdentifier());
       ArrayContentProvider arrayContentProvider = new ArrayContentProvider();
       WorkbenchLabelProvider workbenchLabelProvider = new WorkbenchLabelProvider();
       Shell parentShell = this.parent.getShell();
@@ -323,41 +321,40 @@
          new ListSelectionDialog
                 (
                    parentShell,
-                   projectList,
+                   projectsWithoutNature,
                    arrayContentProvider,
                    workbenchLabelProvider,
                    "Select a project to be configured for building with the CAT."
                 );
       //@formatter:on
-
       int result = listSelectionDialog.open();
-
       if (result == Window.OK) {
          Object[] selectedProjects = listSelectionDialog.getResult();
          @SuppressWarnings("unchecked")
          List<IProject> newProjects = (List<IProject>) (Object) Arrays.asList(selectedProjects);
          return newProjects;
       }
-
       return Collections.emptyList();
-
    }
 
    /**
-    * Callback method for the remove button.
+    * Callback method for the remove button. Removes the selected items in the table view from the selection box.
     */
 
    private void removePressed() {
-      //TODO: remove selection and remove CAT configuration from project
+      List<IProject> selectedProjects = this.getProjectSelectionBox().getSelected();
+      this.getProjectSelectionBox().remove(selectedProjects);
    }
 
    /**
     * Callback method invoked when the selection in the project selection box has changed. This method enables or
     * disables the {@link #buttonBox} buttons according to the {@link #projectSelectionBox} selection state.
+    * 
+    * @implNote Invocation of this method does not indicate that the projects within the {@link #projectSelectionBox}
+    * have changed.
     */
 
    private void selectionChanged() {
-
       this.getButtonBox().enableButtons();
    }
 
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/RequiredNameFileFieldEditor.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/RequiredNameFileFieldEditor.java
new file mode 100644
index 0000000..54ac10d
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/fieldeditors/RequiredNameFileFieldEditor.java
@@ -0,0 +1,117 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.fieldeditors;
+
+import java.io.File;
+import java.util.Objects;
+import org.eclipse.jface.preference.FileFieldEditor;
+import org.eclipse.jface.preference.StringButtonFieldEditor;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * An extension of {@link FileFieldEditor} that requires the selected file to have a case insensitive specific name.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class RequiredNameFileFieldEditor extends FileFieldEditor {
+
+   /**
+    * Saves the required file name converted to lower case.
+    */
+
+   private final String requiredFileName;
+
+   /**
+    * Error message template for the message displayed at top of the preference page when the selected file does not
+    * have the required name.
+    */
+
+   private static final String notRequiredFileNameMessage = "%s Selected file must have the name \"%s\"";
+
+   /**
+    * Error message template for the message displayed at the top of the preference page when a file with the required
+    * name is not selected.
+    */
+
+   private static final String requiredFileNotSelectedMessage = "%s A file with the name \"%s\" must be selected.";
+
+   /**
+    * Error message template for the message displayed at the top of the preference page when the selected file is not a
+    * regular file.
+    */
+
+   private static final String notAFileMessage = "%s Selection must be an existing regular file.";
+
+   /**
+    * Creates a extension of {@link FileFieldEditor} that requires the selected file to have a specific case insensitive
+    * name.
+    *
+    * @param name the name of the preference this field editor works on
+    * @param labelText the label text of the field editor
+    * @param parent the parent of the field editor's control
+    * @param requiredFileName the selected file must have this name in any case.
+    */
+
+   public RequiredNameFileFieldEditor(String name, String labelText, Composite parent, String requiredFileName) {
+      super(name, labelText, true, StringButtonFieldEditor.VALIDATE_ON_FOCUS_LOST, parent);
+      this.requiredFileName = requiredFileName.toLowerCase();
+      String[] requiredNameArray = new String[] {this.requiredFileName};
+      this.setFileExtensions(requiredNameArray);
+   }
+
+   /**
+    * {@inheritDoc}
+    * <p>
+    * Verifies:
+    * <ul>
+    * <li>a selection has been made,</li>
+    * <li>the selection has the required file name, and</li>
+    * <li>the selection references an existing regular file.</li>
+    * </ul>
+    */
+
+   @Override
+   protected boolean checkState() {
+
+      String text = this.getTextControl().getText();
+      String path = Objects.nonNull(text) ? text.trim() : null;
+
+      if (Objects.isNull(path) || path.isEmpty()) {
+         String message = String.format(RequiredNameFileFieldEditor.requiredFileNotSelectedMessage, this.getLabelText(),
+            this.requiredFileName);
+         this.showErrorMessage(message);
+         return false;
+      }
+
+      if (!path.toLowerCase().endsWith(this.requiredFileName)) {
+         String message = String.format(RequiredNameFileFieldEditor.notRequiredFileNameMessage, this.getLabelText(),
+            this.requiredFileName);
+         this.showErrorMessage(message);
+         return false;
+      }
+
+      File file = new File(path);
+
+      if (!file.isFile()) {
+         String message = String.format(RequiredNameFileFieldEditor.notAFileMessage, this.getLabelText());
+         this.showErrorMessage(message);
+         return false;
+      }
+
+      this.clearErrorMessage();
+      return true;
+
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/AbstractCatPreferencePage.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/AbstractCatPreferencePage.java
index 173db7b..bdc8adb 100644
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/AbstractCatPreferencePage.java
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/AbstractCatPreferencePage.java
@@ -14,16 +14,15 @@
 
 import java.util.Arrays;
 import java.util.Objects;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.preference.FieldEditor;
 import org.eclipse.jface.preference.FieldEditorPreferencePage;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.ote.cat.plugin.CatPlugin;
-import org.eclipse.ote.cat.plugin.CatPluginException;
+import org.eclipse.ote.cat.plugin.exception.CatErrorCode;
+import org.eclipse.ote.cat.plugin.exception.CatPluginException;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.IWorkbench;
 import org.eclipse.ui.IWorkbenchPreferencePage;
-import org.eclipse.ui.statushandlers.StatusManager;
 
 /**
  * A base class for CAT Plug-in preference pages.
@@ -46,6 +45,8 @@
 
    protected boolean pageInitialized;
 
+   protected CatPreferences preferenceValues;
+
    /**
     * Creates a new empty {@link FieldEditorPreferencePage} with {@link #GRID} layout.
     * 
@@ -72,15 +73,14 @@
       try {
          IPreferenceStore preferenceStore = CatPlugin.getInstancePreferenceStore();
          this.setPreferenceStore(preferenceStore);
+         this.preferenceValues = Preference.getPreferenceValues(this.preferencePage);
          this.pageInitialized = true;
       } catch (Exception e) {
          //@formatter:off
          CatPluginException initException =
             new CatPluginException
                    (
-                      StatusManager.BLOCK | StatusManager.LOG,
-                      "CAT Plugin Preference Page Error",
-                      IStatus.ERROR,
+                      CatErrorCode.PreferencePageError,
                         "Failed to initialize CAT Plugin preference page." + "\n"
                       + "   Page: " + this.preferencePage.getPageTitle()   + "\n",
                       e
@@ -92,7 +92,7 @@
 
    /**
     * Creates the {@link FieldEditor} instances for the preference page using the factories provided by the members of
-    * the enumeration {@link #Preference}.
+    * the enumeration {@link Preference}.
     * <p>
     * {@inheritDoc}
     */
@@ -120,11 +120,9 @@
          CatPluginException createFieldEditorsException =
             new CatPluginException
                    (
-                      StatusManager.BLOCK | StatusManager.LOG,
-                      "CAT Plugin Preference Page Error",
-                      IStatus.ERROR,
+                      CatErrorCode.PreferencePageError,
                         "Failed to create field editors for CAT Plugin preference page." + "\n"
-                      + "   Page: " + this.preferencePage.getPageTitle()                           + "\n",
+                      + "   Page: " + this.preferencePage.getPageTitle()                 + "\n",
                       e
                    );
          //@formatter:on
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/CatPreferences.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/CatPreferences.java
new file mode 100644
index 0000000..125abdd
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/CatPreferences.java
@@ -0,0 +1,165 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.preferencepage;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.eclipse.ote.cat.plugin.Constants;
+import org.eclipse.ote.cat.plugin.exception.CatErrorCode;
+import org.eclipse.ote.cat.plugin.exception.CatPluginException;
+import org.eclipse.ote.cat.plugin.util.JsonFileOperations;
+
+/**
+ * An object used for deserialization of preference values from a JSON file.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class CatPreferences {
+
+   /**
+    * Save a single instance of a {@link JsonFileOperations} object specialized for reading and writing
+    * {@link CatPreferences} objects to and from JSON files.
+    */
+
+   private static final JsonFileOperations<CatPreferences> jsonFileOperations =
+      new JsonFileOperations<>(CatPreferences.class, "CAT Preferences File");
+
+   /**
+    * Reads a JSON file containing preferences for the CAT Plug-In.
+    * 
+    * @param osPathString the OS path string to the file to be read.
+    * @return {@link CatPreferences} object containing the CAT Plug-In preferences read from the file.
+    * @throws CatPluginException when an error occurs accessing, reading, or parsing the file.
+    */
+
+   public static CatPreferences read(String osPathString) {
+
+      try {
+         Path path = Paths.get(osPathString);
+         File file = path.toFile();
+         return CatPreferences.jsonFileOperations.read(file);
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException failedToReadPreferencesFileException =
+            new CatPluginException
+                   (
+                      CatErrorCode.PreferenceFileError,
+                        "Failed to read preferences file."     + "\n"
+                      + "   Preferences File: " + osPathString + "\n",
+                      e
+                   );
+         //@formatter:on
+         throw failedToReadPreferencesFileException;
+      }
+   }
+
+   @JsonProperty(Constants.catJarPreferenceStoreName)
+   private String catJar;
+
+   @JsonProperty(Constants.jtsProjectsPreferenceStoreName)
+   private String[] jtsProjects;
+
+   @JsonProperty(Constants.pleConfigurationPreferenceStoreName)
+   private String pleConfiguration;
+
+   @JsonProperty(Constants.pleConfigurationCacheFolderPreferenceStoreName)
+   private String pleConfigurationCacheFolder;
+
+   @JsonProperty(Constants.pleConfigurationLoaderPreferenceStoreName)
+   private String pleConfigurationLoader;
+
+   @JsonProperty(Constants.sourceLocationMethodPreferenceStoreName)
+   private String sourceLocationMethod;
+
+   /**
+    * Creates a new {@link CatPreferences} object with all <code>null</code> values.
+    */
+
+   public CatPreferences() {
+      this.catJar = null;
+      this.sourceLocationMethod = null;
+      this.jtsProjects = null;
+      this.pleConfiguration = null;
+      this.pleConfigurationCacheFolder = null;
+      this.pleConfigurationLoader = null;
+   }
+
+   public String getCatJar() {
+      return this.catJar;
+   }
+
+   public String[] getJtsProjects() {
+      return this.jtsProjects;
+   }
+
+   @JsonIgnore
+   public String getJtsProjectsCommaList() {
+      return //
+      Objects.nonNull(this.jtsProjects) //
+         ? Arrays.stream(this.jtsProjects).collect(Collectors.joining(",")) //
+         : null;
+   }
+
+   public String getPleConfiguration() {
+      return this.pleConfiguration;
+   }
+
+   public String getPleConfigurationCacheFolder() {
+      return this.pleConfigurationCacheFolder;
+   }
+
+   public String getPleConfigurationLoader() {
+      return this.pleConfigurationLoader;
+   }
+
+   public String getSourceLocationMethod() {
+      return this.sourceLocationMethod;
+   }
+
+   public void setCatJar(String catJar) {
+      this.catJar = catJar;
+   }
+
+   public void setJtsProjects(String[] jtsProjects) {
+      this.jtsProjects = jtsProjects;
+   }
+
+   @JsonIgnore
+   public void setJtsProjectsCommaList(String jtsProjectsCommaList) {
+      this.jtsProjects = jtsProjectsCommaList.split(",");
+   }
+
+   public void setPleConfiguration(String pleConfiguration) {
+      this.pleConfiguration = pleConfiguration;
+   }
+
+   public void setPleConfigurationCacheFolder(String pleConfigurationCacheFolder) {
+      this.pleConfigurationCacheFolder = pleConfigurationCacheFolder;
+   }
+
+   public void setPleConfigurationLoader(String pleConfigurationLoader) {
+      this.pleConfigurationLoader = pleConfigurationLoader;
+   }
+
+   public void setSourceLocationMethod(String sourceLocationMethod) {
+      this.sourceLocationMethod = sourceLocationMethod;
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/CatSettingsPreferencePage.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/CatSettingsPreferencePage.java
index 77a9842..edb1075 100644
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/CatSettingsPreferencePage.java
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/CatSettingsPreferencePage.java
@@ -12,6 +12,8 @@
 
 package org.eclipse.ote.cat.plugin.preferencepage;
 
+import org.eclipse.ote.cat.plugin.CatPlugin;
+
 /**
  * Implements the top level preference page for the CAT Plug-in settings. This class is instantiated by the Eclipse UI
  * framework before the preference page is displayed.
@@ -30,4 +32,41 @@
       this.setDescription(this.preferencePage.getPageTitle());
    }
 
+   /**
+    * Saves the field editor values in the preference store, saves the preference store, and updates the project natures
+    * according to the {@link Preference#JTS_PROJECTS} preference.
+    */
+
+   @Override
+   public boolean performOk() {
+
+      /*
+       * Saves preferences on this preference page to the preference store.
+       */
+
+      boolean status = super.performOk();
+
+      if (status == false) {
+         return false;
+      }
+
+      /*
+       * Validate the new page values in the preference store and restore the original values when any are invalid.
+       */
+
+      status = Preference.validateValues(this.preferencePage, this.preferenceValues);
+
+      if (status == false) {
+         return false;
+      }
+
+      /*
+       * Update project natures
+       */
+
+      CatPlugin.getCatProjectManager().updateProjectNatures();
+
+      return true;
+   }
+
 }
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/Constants.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/Constants.java
deleted file mode 100644
index 4f26091..0000000
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/Constants.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*********************************************************************
- * Copyright (c) 2024 Boeing
- * 
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * https://www.eclipse.org/legal/epl-2.0/
- * 
- * SPDX-License-Identifier: EPL-2.0
- * 
- * Contributors: Boeing - initial API and implementation
- **********************************************************************/
-
-package org.eclipse.ote.cat.plugin.preferencepage;
-
-/**
- * This package private class contains constants used within the package.
- * 
- * @author Loren K. Ashley
- * @implNote The preference store names of the plug-in preferences are also used as object names in the default
- * preference value JSON file. They are defined as constants so that they may be used as
- * {@link com.fasterxml.jackson.annotation} values. For general access, the method
- * {@link Preference#getPreferenceStoreName} should be used to obtain the preference store names.
- */
-
-class Constants {
-
-   static final String catJarPreferenceStoreName = "CAT_JAR_PATH";
-   static final String sourceLocationMethodPreferenceStoreName = "SOURCE_LOCATION_METHOD";
-   static final String jtsProjectsPreferenceStoreName = "JTS_PROJECTS";
-   static final String pleConfigurationPreferenceStoreName = "PLE_CONFIGURATION";
-   static final String pleConfigurationCacheFolderPreferenceStoreName = "PLE_CONFIGURATION_CACHE_FOLDER";
-   static final String pleConfigurationLoaderPreferenceStoreName = "OPLE_SERVER";
-
-}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/DefaultPreferences.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/DefaultPreferences.java
deleted file mode 100644
index ca4b335..0000000
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/DefaultPreferences.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*********************************************************************
- * Copyright (c) 2024 Boeing
- * 
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * https://www.eclipse.org/legal/epl-2.0/
- * 
- * SPDX-License-Identifier: EPL-2.0
- * 
- * Contributors: Boeing - initial API and implementation
- **********************************************************************/
-
-package org.eclipse.ote.cat.plugin.preferencepage;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.Arrays;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-/**
- * A POJO for deserialization of default preference values from a JSON file.
- * 
- * @author Loren K. Ashley
- */
-
-public class DefaultPreferences {
-
-   @JsonProperty(Constants.catJarPreferenceStoreName)
-   private String catJar;
-
-   @JsonProperty(Constants.jtsProjectsPreferenceStoreName)
-   private String[] jtsProjects;
-
-   @JsonProperty(Constants.pleConfigurationPreferenceStoreName)
-   private String pleConfiguration;
-
-   @JsonProperty(Constants.pleConfigurationCacheFolderPreferenceStoreName)
-   private String pleConfigurationCacheFolder;
-
-   @JsonProperty(Constants.pleConfigurationLoaderPreferenceStoreName)
-   private String pleConfigurationLoader;
-
-   @JsonProperty(Constants.sourceLocationMethodPreferenceStoreName)
-   private String sourceLocationMethod;
-
-   public DefaultPreferences() {
-      this.catJar = null;
-      this.sourceLocationMethod = null;
-      this.jtsProjects = null;
-      this.pleConfiguration = null;
-      this.pleConfigurationCacheFolder = null;
-      this.pleConfigurationLoader = null;
-   }
-
-   public String getCatJar() {
-      return this.catJar;
-   }
-
-   public String[] getJtsProjects() {
-      return this.jtsProjects;
-   }
-
-   @JsonIgnore
-   public String getJtsProjectsCommaList() {
-      return //
-      Objects.nonNull(this.jtsProjects) //
-         ? Arrays.stream(this.jtsProjects).collect(Collectors.joining(",")) //
-         : null;
-   }
-
-   public String getPleConfiguration() {
-      return this.pleConfiguration;
-   }
-
-   public String getPleConfigurationCacheFolder() {
-      return this.pleConfigurationCacheFolder;
-   }
-
-   public String getPleConfigurationLoader() {
-      return this.pleConfigurationLoader;
-   }
-
-   public String getSourceLocationMethod() {
-      return this.sourceLocationMethod;
-   }
-
-   public void setCatJar(String catJar) {
-      this.catJar = catJar;
-   }
-
-   public void setJtsProjects(String[] jtsProjects) {
-      this.jtsProjects = jtsProjects;
-   }
-
-   public void setPleConfiguration(String pleConfiguration) {
-      this.pleConfiguration = pleConfiguration;
-   }
-
-   public void setPleConfigurationCacheFolder(String pleConfigurationCacheFolder) {
-      this.pleConfigurationCacheFolder = pleConfigurationCacheFolder;
-   }
-
-   public void setPleConfigurationLoader(String pleConfigurationLoader) {
-      this.pleConfigurationLoader = pleConfigurationLoader;
-   }
-
-   public void setSourceLocationMethod(String sourceLocationMethod) {
-      this.sourceLocationMethod = sourceLocationMethod;
-   }
-
-}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/PleConfigurationCachePreferencePage.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/PleConfigurationCachePreferencePage.java
index ac5de67..80c4667 100644
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/PleConfigurationCachePreferencePage.java
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/PleConfigurationCachePreferencePage.java
@@ -26,7 +26,7 @@
     */
 
    public PleConfigurationCachePreferencePage() {
-      super(PreferencePage.PLE_CONFIGURATION_CACHE);
+      super(PreferencePage.PLE_CONFIGURATION_LOADER);
       this.setDescription(this.preferencePage.getPageTitle());
    }
 
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/Preference.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/Preference.java
index 1c21775..a5d3109 100644
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/Preference.java
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/Preference.java
@@ -16,43 +16,41 @@
 import java.nio.file.Paths;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.BiConsumer;
 import java.util.function.Function;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.preference.ComboFieldEditor;
-import org.eclipse.jface.preference.DirectoryFieldEditor;
 import org.eclipse.jface.preference.FieldEditor;
-import org.eclipse.jface.preference.FileFieldEditor;
 import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.StringButtonFieldEditor;
 import org.eclipse.ote.cat.plugin.CatPlugin;
-import org.eclipse.ote.cat.plugin.CatPluginException;
-import org.eclipse.ote.cat.plugin.fieldeditors.DirectoryAutoStore;
-import org.eclipse.ote.cat.plugin.fieldeditors.PleConfigurationLoader;
-import org.eclipse.ote.cat.plugin.fieldeditors.Project;
+import org.eclipse.ote.cat.plugin.Constants;
+import org.eclipse.ote.cat.plugin.exception.CatErrorCode;
+import org.eclipse.ote.cat.plugin.exception.CatPluginException;
+import org.eclipse.ote.cat.plugin.fieldeditors.DirectoryAutoStoreFieldEditor;
+import org.eclipse.ote.cat.plugin.fieldeditors.PleConfigurationLoaderFieldEditor;
+import org.eclipse.ote.cat.plugin.fieldeditors.ProjectFieldEditor;
+import org.eclipse.ote.cat.plugin.fieldeditors.RequiredNameFileFieldEditor;
 import org.eclipse.swt.widgets.Composite;
-import org.eclipse.ui.part.Page;
-import org.eclipse.ui.statushandlers.StatusManager;
 
 /**
  * Enumeration of the preferences managed by the CAT Plugin for the CAT annotation processor. The enumeration members
  * are used to provide:
  * <ul>
- * <li>the preference page that will contain the preference editor for that preference,
- * <li>
- * <li>the preference store name for the preference,
- * <li>
+ * <li>the preference page that will contain the preference editor for that preference,</li>
+ * <li>the preference store name for the preference,</li>
  * <li>the field editor label on the preference page for the preference, and</li>
  * <li>a factory method for creating the preference page {@link FieldEditor} for the preference.</li>
  * </ul>
- * 
- * @see
+ * <h3>See Also:</h3>
  * <ul>
- * <li>{@link Preferences.Preference#CAT_JAR CAT_JAR}</li>
- * <li>{@link Preferences.Preference#JTS_PROJECTS JTS_PROJECTS}</li>
- * <li>{@link Preferences.Preference#PLE_CONFIGURATION PLE_CONFIGURATION}</li>
- * <li>{@link Preferences.Preference#PLE_CONFIGURATION_CACHE_FOLDER PLE_CONFIGURATION_CACHE_FOLDER}</li>
- * <li>{@link Preferences.Preference#PLE_CONFIGURATION_LOADER PLE_CONFIGURATION_LOADER_PREFERENCE}</li>
- * <li>{@link Preferences.Preference#SOURCE_LOCATION_METHOD SOURCE_LOCATION_METHOD}</li>
+ * <li>{@link Preference#CAT_JAR CAT_JAR}</li>
+ * <li>{@link Preference#JTS_PROJECTS JTS_PROJECTS}</li>
+ * <li>{@link Preference#PLE_CONFIGURATION PLE_CONFIGURATION}</li>
+ * <li>{@link Preference#PLE_CONFIGURATION_CACHE_FOLDER PLE_CONFIGURATION_CACHE_FOLDER}</li>
+ * <li>{@link Preference#PLE_CONFIGURATION_LOADER PLE_CONFIGURATION_LOADER_PREFERENCE}</li>
+ * <li>{@link Preference#SOURCE_LOCATION_METHOD SOURCE_LOCATION_METHOD}</li>
  * </ul>
+ * 
  * @implNote The preference {@link FieldEditor}s will appear on the preference pages in the ordinal order of the
  * enumeration members.
  */
@@ -63,8 +61,8 @@
    /**
     * This preference is used to specify the location of the CAT Jar file.
     * <dl>
-    * <dt>{@link Page}:</dt>
-    * <dd>{@link Page#CAT CAT}</dt>
+    * <dt>{@link PreferencePage Page}:</dt>
+    * <dd>{@link PreferencePage#CAT_SETTINGS CAT_SETTINGS}</dd>
     * <dt>Preference Store Name:</dt>
     * <dd>{@value Constants#catJarPreferenceStoreName}</dd>
     * </dl>
@@ -75,8 +73,8 @@
          PreferencePage.CAT_SETTINGS,
          Constants.catJarPreferenceStoreName,
          "CAT Jar File:",
-         DefaultPreferences::getCatJar
-         
+         CatPreferences::getCatJar,
+         CatPreferences::setCatJar
       ) {
 
       /**
@@ -85,17 +83,19 @@
       
       public FieldEditor createFieldEditorInternal(Composite parentComposite) {
          return
-            new FileFieldEditor(this.getPreferenceStoreName(),this.getFieldEditorLabel(), parentComposite);
+            new RequiredNameFileFieldEditor(this.getPreferenceStoreName(),this.getFieldEditorLabel(), parentComposite, Constants.catJarDetectionName);
       }
       
       /**
-       * Validates the default <code>value</code> is the path of a file that can be read.
+       * Validates the <code>value</code> is the path of a file that can be read.
        * <p>
        * {@inheritDoc}
+       * @param value the OS {@link String} path to the file to be tested.
+       * @param isDefault indicates if the <code>value</code> is a default preference value.
        * @return <code>true</code> when the <code>value</code> is the path of a readable file; otherwise, <code>false</code>.
        */
       
-      public boolean validateDefaultValue(String value) {
+      public boolean validateValue(String value,IsDefault isDefault) {
          
          Exception cause = null;
          
@@ -107,17 +107,35 @@
          } catch( Exception e ) {
             cause = e;
          }
+
+         final String formatMessage =
+              "The CAT Jar file %sdoes not exist or cannot be read." + "\n"
+            + "   %sCat Jar File: " + value                          + "\n"
+                                                                     + "\n"
+            + "%s";
+         
+         final String message =
+            isDefault.isYes()
+               ? String.format
+                    (
+                       formatMessage,
+                       "at the default location",
+                       "Default",
+                       "The default value for the preference \"" + this.getPreferenceStoreName() + "\" will be ignored." + "\n"
+                    )
+               : String.format
+                    (
+                       formatMessage,
+                       "", 
+                       "",
+                       "" 
+                    );
          
          CatPluginException catJarDefaultValueException =
             new CatPluginException
                    (
-                      StatusManager.BLOCK | StatusManager.LOG,
-                      "CAT Plugin Default Preferences",
-                      IStatus.WARNING,
-                        "The CAT Jar file at the default location does not exist or cannot be read."                      + "\n"
-                      + "   Default Cat Jar File: " + value                                                               + "\n"
-                                                                                                                          + "\n"
-                      + "The default value for the preference \"" + this.getPreferenceStoreName() + "\" will be ignored." + "\n",
+                      CatErrorCode.CatJarFileError,
+                      message,
                       cause
                    );
 
@@ -128,10 +146,241 @@
    },
 
    /**
+    * This preferences is used to select the Eclipse Java Projects that will be configured to use the CAT.
+    * <dl>
+    * <dt>{@link PreferencePage Page}:</dt>
+    * <dd>{@link PreferencePage#CAT_SETTINGS CAT_SETTINGS}</dd>
+    * <dt>Preference Store Name:</dt>
+    * <dd>{@value Constants#jtsProjectsPreferenceStoreName}</dd>
+    * </dl>
+    */
+
+   JTS_PROJECTS
+      (
+         PreferencePage.CAT_SETTINGS,
+         Constants.jtsProjectsPreferenceStoreName,
+         "Java Test Script Projects:",
+         CatPreferences::getJtsProjectsCommaList,
+         CatPreferences::setJtsProjectsCommaList
+      ) {
+      
+      /**
+       * {@inheritDoc}
+       */
+      
+      @Override
+      public FieldEditor createFieldEditorInternal(Composite parentComposite) {
+         return
+            new ProjectFieldEditor(this.getPreferenceStoreName(), this.getFieldEditorLabel(), parentComposite);
+      }
+      
+      /**
+       * {@inheritDoc}
+       */
+      
+      public boolean validateValue(String value, IsDefault isDefault) {
+         //TODO: This will be completed when JTS project management is implemented.
+         return true;
+      }
+   },
+         
+   /**
+    * This preference is used to select the PLE Configuration to be used with the CAT/BAT.
+    * <dl>
+    * <dt>{@link PreferencePage Page}:</dt>
+    * <dd>{@link PreferencePage#CAT_SETTINGS CAT_SETTINGS}</dd>
+    * <dt>Preference Store Name:</dt>
+    * <dd>{@value Constants#pleConfigurationPreferenceStoreName}</dd>
+    * </dl>
+    */
+
+   PLE_CONFIGURATION
+      (
+         PreferencePage.CAT_SETTINGS,
+         Constants.pleConfigurationPreferenceStoreName,
+         "PLE Configuration:",
+         CatPreferences::getPleConfiguration,
+         CatPreferences::setPleConfiguration
+      ) {
+   
+      private static final String buttonLabel = "Select";
+      
+      /**
+       * {@inheritDoc}
+       */
+      
+      @Override
+      public FieldEditor createFieldEditorInternal(Composite parentComposite) {
+         final StringButtonFieldEditor stringButtonFieldEditor =
+            new StringButtonFieldEditor(this.getPreferenceStoreName(), this.getFieldEditorLabel(), parentComposite) {
+            protected String changePressed() {
+               return "ABCDEFG";
+            }
+         };
+         stringButtonFieldEditor.setChangeButtonText(buttonLabel);
+         return stringButtonFieldEditor;
+         
+      }
+      
+      /**
+       * This <code>value</code> is always accepted as it will not cause an invalid preference page error.
+       * <p> 
+       * {@inheritDoc}
+       * @return <code>true</code>
+       */
+      
+      public boolean validateValue(String value, IsDefault isDefault) {
+         return true;
+      }
+   },
+
+   /**
+    * This preference is used to select the folder used to save PLE Configurations.
+    * <dl>
+    * <dt>{@link PreferencePage Page}:</dt>
+    * <dd>{@link PreferencePage#CAT_SETTINGS CAT_SETTINGS}</dd>
+    * <dt>Preference Store Name:</dt>
+    * <dd>{@value Constants#pleConfigurationCacheFolderPreferenceStoreName}</dd>
+    * </dl>
+    */
+
+   PLE_CONFIGURATION_CACHE_FOLDER
+      (
+         PreferencePage.CAT_SETTINGS,
+         Constants.pleConfigurationCacheFolderPreferenceStoreName,
+         "PLE Configuration Cache Folder:",
+         CatPreferences::getPleConfigurationCacheFolder,
+         CatPreferences::setPleConfigurationCacheFolder
+      ) {
+      
+      /**
+       * {@inheritDoc}
+       */
+      
+      @Override
+      public FieldEditor createFieldEditorInternal(Composite parentComposite) {
+         return
+            new DirectoryAutoStoreFieldEditor(this.getPreferenceStoreName(),this.getFieldEditorLabel(), parentComposite);
+      }
+      
+      /**
+       * Validates the <code>value</code> is the path of a directory that can be read.
+       * <p>
+       * {@inheritDoc}
+       * @param value the OS {@link String} path to the directory to be tested.
+       * @param isDefault indicates if the <code>value</code> is a default preference value.
+       * @return <code>true</code> when the <code>value</code> is the path of a directory that can be read; otherwise, <code>false</code>.
+       */
+      
+      public boolean validateValue(String value, IsDefault isDefault) {
+         
+         Exception cause = null;
+         String reason = null;
+         
+         try {
+         
+            File file = Paths.get(value).toFile();
+            
+            if( !file.canRead() ) {
+               reason = "cannot be read";
+            } else if( !file.isDirectory() ) {
+               reason = "is not a directory";
+            }
+            
+         } catch( Exception e ) {
+            cause = e;
+            reason = "cannot be evaluated";
+         }
+         
+         if( Objects.isNull(cause) && Objects.isNull(reason)) {
+            return true;
+         }
+         
+         final String formatMessage =
+              "The PLE Configuration Cache Folder %s%s."       + "\n"
+            + "   %sPLE Configuration Cache Folder: " + value  + "\n"
+                                                               + "\n"
+            + "%s";
+            
+         final String message =
+            isDefault.isYes()
+               ? String.format
+                    (
+                       formatMessage, 
+                       "at the default location ",
+                       reason,
+                       "Default",
+                       "The default value for the preference \"" + this.getPreferenceStoreName() + "\" will be ignored." + "\n"                       
+                    )
+               : String.format
+                    (
+                       formatMessage, 
+                       "",
+                       reason,
+                       "",
+                       ""
+                    );
+            
+         CatPluginException catJarDefaultValueException =
+            new CatPluginException
+                   (
+                      CatErrorCode.PleConfigurationCacheFolderError,
+                      message,
+                      cause
+                   );
+
+         catJarDefaultValueException.log();
+         
+         return false;
+      }
+   }, 
+      
+   /**
+    * This preference is used save the OPLE server PLE Configurations are down loaded from and provides a dialog for
+    * down loading PLE Configurations.
+    * <dl>
+    * <dt>{@link PreferencePage Page}:</dt>
+    * <dd>{@link PreferencePage#PLE_CONFIGURATION_LOADER PLE_CONFIGURATION_LOADER}</dd>
+    * <dt>Preference Store Name:</dt>
+    * <dd>{@value Constants#pleConfigurationLoaderPreferenceStoreName}</dd>
+    * </dl>
+    */
+
+   PLE_CONFIGURATION_LOADER
+      (
+         PreferencePage.PLE_CONFIGURATION_LOADER, 
+         Constants.pleConfigurationLoaderPreferenceStoreName,
+         "OPLE Server:",
+         CatPreferences::getPleConfigurationLoader,
+         CatPreferences::setPleConfigurationLoader
+      ) {
+      
+      /**
+       * {@inheritDoc}
+       */
+      
+      public FieldEditor createFieldEditorInternal(Composite parentComposite) {
+         return
+            new PleConfigurationLoaderFieldEditor(this.getPreferenceStoreName(), this.getFieldEditorLabel(), parentComposite );
+      }
+      
+      /**
+       * This <code>value</code> is always accepted as it will not cause an invalid preference page error.
+       * <p> 
+       * {@inheritDoc}
+       * @return <code>true</code>
+       */
+      
+      public boolean validateValue(String value, IsDefault isDefault) {
+         return true;
+      }
+   },
+      
+   /**
     * This preference is used to select the method by which the CAT will find source code files.
     * <dl>
-    * <dt>{@link Page}:</dt>
-    * <dd>{@link Page#CAT CAT}</dt>
+    * <dt>{@link PreferencePage Page}:</dt>
+    * <dd>{@link PreferencePage#CAT_SETTINGS CAT_SETTINGS}</dd>
     * <dt>Preference Store Name:</dt>
     * <dd>{@value Constants#sourceLocationMethodPreferenceStoreName}</dd>
     * </dl>
@@ -142,14 +391,15 @@
          PreferencePage.CAT_SETTINGS,
          Constants.sourceLocationMethodPreferenceStoreName,
          "Source Location Method:",
-         DefaultPreferences::getSourceLocationMethod
+         CatPreferences::getSourceLocationMethod,
+         CatPreferences::setSourceLocationMethod
       ) {
       
       private final String[][] sourceLocationMethods =
          {
-            { "Eclipse IDE", "A" },
-            { "Javac",       "B" },
-            { "Maven",       "C" }
+            { "Eclipse IDE", "Eclipse IDE" },
+            { "Javac",       "Javac" },
+            { "Maven",       "Maven" }
          };
 
       /**
@@ -166,10 +416,12 @@
        * Validates the default Source Location Method is a supported Source Location Method.
        * <p>
        * {@inheritDoc}
+       * @param value the Source Location Method {@link String} to be tested.
+       * @param isDefault indicates if the <code>value</code> is a default preference value.
        * @return <code>true</code> when the value is a valid Source Location Method; otherwise, <code>false</code>.
        */
       
-      public boolean validateDefaultValue(String value) {
+      public boolean validateValue(String value, IsDefault isDefault) {
          for( int i = 0; i < sourceLocationMethods.length; i++ ) {
             if( sourceLocationMethods[i][0].equals( value ) ) {
                return true;
@@ -181,258 +433,131 @@
             validValues.append( "      " ).append( sourceLocationMethods[i][0] ).append( "\n" );
          }
          
+         final String formatMessage =
+              "The Source Location Method %sis not a valid Source Location Method." + "\n"
+            + "   %sSource Location Method: " + value                               + "\n"
+            + "   Valid Source Location Methods: "                                  + "\n"
+            + validValues.toString()
+            + "%s";
+         
+         final String message =
+            isDefault.isYes()
+               ? String.format
+                    (
+                       formatMessage, 
+                       "specified in the default preferences file ",
+                       "Default",
+                       "The default value for the preference \"" + this.getPreferenceStoreName() + "\" will be ignored." + "\n"
+                    )
+               : String.format
+                    (
+                       formatMessage, 
+                       "",
+                       "",
+                       ""
+                    );
+            
          CatPluginException sourceLocationMethodDefaultValueException =
             new CatPluginException
                    (
-                      StatusManager.BLOCK | StatusManager.LOG,
-                      "CAT Plugin Default Preferences",
-                      IStatus.WARNING,
-                        "The Source Location Method specified in the default preferences file is not a valid Source Location Method." + "\n"
-                      + "   Specified Default Source Location Method: " + value                                                       + "\n"
-                      + "   Valid Source Location Methods: "                                                                          + "\n"
-                      + validValues.toString(),
-                      null
+                      CatErrorCode.SourceLocationMethodError,
+                      message
                    );
          
          sourceLocationMethodDefaultValueException.log();
          
          return false;
       }
-   },
-         
-   /**
-    * This preferences is used to select the Eclipse Java Projects that will be configured to use the CAT.
-    * <dl>
-    * <dt>{@link Page}:</dt>
-    * <dd>{@link Page#CAT CAT}</dt>
-    * <dt>Preference Store Name:</dt>
-    * <dd>{@value Constants#jtsProjectsPreferenceStoreName}</dd>
-    * </dl>
-    */
-
-   JTS_PROJECTS
-      (
-         PreferencePage.CAT_SETTINGS,
-         Constants.jtsProjectsPreferenceStoreName,
-         "Java Test Script Projects:",
-         DefaultPreferences::getJtsProjectsCommaList
-      ) {
-      
-      /**
-       * {@inheritDoc}
-       */
-      
-      @Override
-      public FieldEditor createFieldEditorInternal(Composite parentComposite) {
-         return
-            new Project(this.getPreferenceStoreName(), this.getFieldEditorLabel(), parentComposite);
-      }
-      
-      /**
-       * {@inheritDoc}
-       */
-      
-      public boolean validateDefaultValue(String value) {
-         //TODO: This will be completed when JTS project management is implemented.
-         return true;
-      }
-   },
-
-   /**
-    * This preference is used to select the PLE Configuration to be used with the CAT/BAT.
-    * <dl>
-    * <dt>{@link Page}:</dt>
-    * <dd>{@link Page#CAT CAT}</dt>
-    * <dt>Preference Store Name:</dt>
-    * <dd>{@value Constants#pleConfigurationPreferenceStoreName}</dd>
-    * </dl>
-    */
-
-   PLE_CONFIGURATION
-      (
-         PreferencePage.CAT_SETTINGS,
-         Constants.pleConfigurationPreferenceStoreName,
-         "PLE Configuration:",
-         DefaultPreferences::getPleConfiguration
-      ) {
-   
-      private static final String buttonLabel = "Select";
-      
-      /**
-       * {@inheritDoc}
-       */
-      
-      @Override
-      public FieldEditor createFieldEditorInternal(Composite parentComposite) {
-         final DirectoryFieldEditor directoryFieldEditor =
-            new DirectoryFieldEditor(this.getPreferenceStoreName(), this.getFieldEditorLabel(), parentComposite);
-         directoryFieldEditor.setChangeButtonText(buttonLabel);
-         return directoryFieldEditor;
-         
-      }
-      
-      /**
-       * This <code>value</code> is always accepted as it will not cause an invalid preference page error.
-       * <p> 
-       * {@inheritDoc}
-       * @return <code>true</code>
-       */
-      
-      public boolean validateDefaultValue(String value) {
-         return true;
-      }
-   },
-      
-   /**
-    * This preference is used to select the folder used to save PLE Configurations.
-    * <dl>
-    * <dt>{@link Page}:</dt>
-    * <dd>{@link Page#PLE_CONFIGURATION_CACHE PLE_CONFIGURATION_CACHE}</dt>
-    * <dt>Preference Store Name:</dt>
-    * <dd>{@value Constants#pleConfigurationCacheFolderPreferenceStoreName}</dd>
-    * </dl>
-    */
-
-   PLE_CONFIGURATION_CACHE_FOLDER
-      (
-         PreferencePage.PLE_CONFIGURATION_CACHE,
-         Constants.pleConfigurationCacheFolderPreferenceStoreName,
-         "Folder:",
-         DefaultPreferences::getPleConfigurationCacheFolder
-      ) {
-      
-      /**
-       * {@inheritDoc}
-       */
-      
-      @Override
-      public FieldEditor createFieldEditorInternal(Composite parentComposite) {
-         return
-            new DirectoryAutoStore(this.getPreferenceStoreName(),this.getFieldEditorLabel(), parentComposite);
-      }
-      
-      /**
-       * Validates the default <code>value</code> is the path of a directory that can be read.
-       * <p>
-       * {@inheritDoc}
-       * @return <code>true</code> when the <code>value</code> is the path of a directory that can be read; otherwise, <code>false</code>.
-       */
-      
-      public boolean validateDefaultValue(String value) {
-         
-         Exception cause = null;
-         
-         try {
-         
-            File file = Paths.get(value).toFile();
-            
-            if( !file.canRead() ) {
-               cause = new RuntimeException();
-            } else if( !file.isDirectory() ) {
-               cause = new RuntimeException();
-            }
-            
-         } catch( Exception e ) {
-            cause = e;
-         }
-         
-         if( Objects.isNull(cause) ) {
-            return true;
-         }
-         
-         CatPluginException catJarDefaultValueException =
-            new CatPluginException
-                   (
-                      StatusManager.BLOCK | StatusManager.LOG,
-                      "CAT Plugin Default Preferences",
-                      IStatus.WARNING,
-                        "The PLE Configuration Cache Folder at the default location does not exist or cannot be read."                      + "\n"
-                      + "   Default PLE Configuration Cache Folder: " + value                                             + "\n"
-                                                                                                                          + "\n"
-                      + "The default value for the preference \"" + this.getPreferenceStoreName() + "\" will be ignored." + "\n",
-                      cause
-                   );
-
-         catJarDefaultValueException.log();
-         
-         return false;
-      }
-   }, 
-
-   /**
-    * This preference is used save the OPLE server PLE Configurations are down loaded from and provides a dialog for
-    * down loading PLE Configurations.
-    * <dl>
-    * <dt>{@link Page}:</dt>
-    * <dd>{@link Page#PLE_CONFIGURATION_CACHE PLE_CONFIGURATION_CACHE}</dt>
-    * <dt>Preference Store Name:</dt>
-    * <dd>{@value Constants#pleConfigurationLoaderPreferenceStoreName}</dd>
-    * </dl>
-    */
-
-   PLE_CONFIGURATION_LOADER
-      (
-         PreferencePage.PLE_CONFIGURATION_CACHE, 
-         Constants.pleConfigurationLoaderPreferenceStoreName,
-         "OPLE Server:",
-         DefaultPreferences::getPleConfigurationLoader
-      ) {
-      
-      /**
-       * {@inheritDoc}
-       */
-      
-      public FieldEditor createFieldEditorInternal(Composite parentComposite) {
-         return
-            new PleConfigurationLoader(this.getPreferenceStoreName(), this.getFieldEditorLabel(), parentComposite );
-      }
-      
-      /**
-       * This <code>value</code> is always accepted as it will not cause an invalid preference page error.
-       * <p> 
-       * {@inheritDoc}
-       * @return <code>true</code>
-       */
-      
-      public boolean validateDefaultValue(String value) {
-         return true;
-      }
    };
    //@formatter:on
 
    /**
-    * Creates the {@link FieldEditor} implementation for the CAT Plugin preference represented by the enumeration
-    * member.
+    * Indicator enumeration use to specify when a preference value is a default value.
+    */
+
+   enum IsDefault {
+
+      /**
+       * The preference value is not a default.
+       */
+
+      NO,
+
+      /**
+       * The preference value is a default value.
+       */
+
+      YES;
+
+      /**
+       * Predicate to determine if the {@link IsDefault} member is {@link IsDefault#YES YES}.
+       * 
+       * @return <code>true</code> when the member is {@link IsDefault#YES}; otherwise, <code>false</code>.
+       */
+
+      boolean isYes() {
+         return this == YES;
+      }
+   }
+
+   /**
+    * Creates a {@link CatPreferences} and sets it with the preference value from the <code>preferencePage</code>.
     * 
-    * @param parentComposite the {@link Composite} of the preference page the {@link FieldEditor} will be attached to.
-    * @return a {@link FieldEditor} implementation.
+    * @param preferencePage only set the preferences from this page.
+    * @return a {@link CatPreferences} set with the value from the <code>preferencePage</code>.
     */
 
-   abstract FieldEditor createFieldEditorInternal(Composite parentComposite);
+   public static CatPreferences getPreferenceValues(PreferencePage preferencePage) {
+      CatPreferences catPreferences = new CatPreferences();
+      for (Preference preference : Preference.values()) {
+         if (preference.isOnPage(preferencePage)) {
+            String value = preference.get();
+            preference.preferencePojoValueSetter.accept(catPreferences, value);
+         }
+      }
+      return catPreferences;
+   }
 
    /**
-    * Determines if the default value read from the default preferences file is OK to be set as the default value. The
-    * user may be presented with the option to correct the situation that makes the default value invalid. However, the
-    * user cannot change the default value.
+    * Updates the preference store with the preference values from the <code>preferencesPojo</code> for the preferences
+    * on the <code>preferencePage</code>.
     * 
-    * @param value the default value for the preference to be tested.
-    * @return <code>true</code> when it is OK to set the <code>value</code> as the default value in the preference
-    * store; otherwise, <code>false</code>.
+    * @param preferencePage only the preferences from this page will be set in the preference store.
+    * @param catPreferences preference values to be set.
     */
 
-   abstract boolean validateDefaultValue(String value);
+   public static void setPreferenceValues(PreferencePage preferencePage, CatPreferences catPreferences) {
+      for (Preference preference : Preference.values()) {
+         if (preference.isOnPage(preferencePage)) {
+            String value = preference.preferencePojoValueGetter.apply(catPreferences);
+            preference.set(value);
+         }
+      }
+   }
 
    /**
-    * Saves the key (name) used to access the preference value in the {@link IPrefernceStore}.
+    * Validates the preference values in the preference store for the <code>preferencePage</code>. If any of the
+    * validations fail, the preference store is restored with the values from <code>originalPreferenceValues</code> for
+    * the <code>preferencePage</code>.
+    * 
+    * @param preferencePage the preference values on this page are validated.
+    * @param originalPreferenceValues restoration preference value for the page.
+    * @return <code>true</code> when all values on the page are validated; otherwise, <code>false</code>.
     */
 
-   public final String preferenceStoreName;
-
-   /**
-    * Saves the preference page that the preference's field editor will appear on.
-    */
-
-   private final PreferencePage page;
+   public static boolean validateValues(PreferencePage preferencePage, CatPreferences originalPreferenceValues) {
+      boolean status = true;
+      for (Preference preference : Preference.values()) {
+         if (preference.isOnPage(preferencePage)) {
+            String value = preference.get();
+            status &= preference.validateValue(value, IsDefault.NO);
+         }
+      }
+      if (status == false) {
+         Preference.setPreferenceValues(preferencePage, originalPreferenceValues);
+      }
+      return status;
+   }
 
    /**
     * Saves the label used for the preference's field editor.
@@ -441,22 +566,28 @@
    private final String fieldEditorLabel;
 
    /**
-    * Saves the {@link Function} used to extract the preference value from a {@link DefaultPreferences} POJO.
+    * Saves the preference page that the preference's field editor will appear on.
     */
 
-   private final Function<DefaultPreferences, String> defaultPreferenceValueExtractor;
+   private final PreferencePage page;
 
    /**
-    * Predicate to determine if the preference's field editor appears on the specified <code>page</code>.
-    * 
-    * @param page the {@link PreferencePage} to be tested.
-    * @return <code>true</code> when the preference appears on the specified <code>page</code>; otherwise,
-    * <code>null</code>.
+    * Saves the {@link Function} used to get a preference value from a {@link CatPreferences}.
     */
 
-   public boolean isOnPage(PreferencePage page) {
-      return this.page == page;
-   }
+   private final Function<CatPreferences, String> preferencePojoValueGetter;
+
+   /**
+    * Saves the {@link BiConsumer} used to set a preference value in to a {@link PreferencePojo}.
+    */
+
+   private final BiConsumer<CatPreferences, String> preferencePojoValueSetter;
+
+   /**
+    * Saves the key (name) used to access the preference value in the {@link IPreferenceStore}.
+    */
+
+   public final String preferenceStoreName;
 
    /**
     * Private enumeration member constructor used to save the represented preference's parameters.
@@ -465,11 +596,11 @@
     * @param preferenceStoreName the key (name) used to access the preference's value in the Cat Plugin's preference
     * store.
     * @param fieldEditorLabel the label used for the preference's field editor upon the preference page.
-    * @param defaultPreferenceValueExtractor a {@link Function} used to extract the default value for the preference
-    * from a {@link DefaultPreferences} POJO
+    * @param preferencePojoValueGetter a {@link Function} used to extract the default value for the preference from a
+    * {@link CatPreferences} POJO
     */
 
-   private Preference(PreferencePage page, String preferenceStoreName, String fieldEditorLabel, Function<DefaultPreferences, String> defaultPreferenceValueExtractor) {
+   private Preference(PreferencePage page, String preferenceStoreName, String fieldEditorLabel, Function<CatPreferences, String> preferencePojoValueGetter, BiConsumer<CatPreferences, String> preferencePojoValueSetter) {
       //@formatter:off
       assert 
            Objects.nonNull(page)
@@ -487,7 +618,9 @@
       this.page = page;
       this.preferenceStoreName = preferenceStoreName;
       this.fieldEditorLabel = fieldEditorLabel;
-      this.defaultPreferenceValueExtractor = defaultPreferenceValueExtractor;
+      this.preferencePojoValueGetter = preferencePojoValueGetter;
+      this.preferencePojoValueSetter = preferencePojoValueSetter;
+
    }
 
    /**
@@ -508,9 +641,7 @@
          CatPluginException createFieldEditorException =
             new CatPluginException
                    (
-                      IStatus.ERROR,
-                      "CAT Plugin Error",
-                      0,
+                      CatErrorCode.PreferencePageError,
                         "Failed to create field editor for preference." + "\n"
                       + "   Preference: " + this                        + "\n",
                       e
@@ -521,10 +652,20 @@
    }
 
    /**
+    * Creates the {@link FieldEditor} implementation for the CAT Plugin preference represented by the enumeration
+    * member.
+    * 
+    * @param parentComposite the {@link Composite} of the preference page the {@link FieldEditor} will be attached to.
+    * @return a {@link FieldEditor} implementation.
+    */
+
+   abstract FieldEditor createFieldEditorInternal(Composite parentComposite);
+
+   /**
     * Gets the value of the preference as a {@link String}.
     * 
     * @return the preference value.
-    * @throws CatPluginException when unable to get the preference value from the plug-in preference store.
+    * @throws CatPluginUserException when unable to get the preference value from the plug-in preference store.
     */
 
    public String get() {
@@ -541,11 +682,9 @@
          throw
             new CatPluginException
                    (
-                      StatusManager.BLOCK | StatusManager.LOG,
-                      "CAT Plugin Preference Error",
-                      IStatus.ERROR,
-                        "Unable to get preference."  + "\n"
-                      + "Preference: " + this.name() + "\n",
+                      CatErrorCode.InternalError,
+                        "Unable to get preference from preference store." + "\n"
+                      + "Preference: " + this.name()                      + "\n",
                       e
                    );
       }
@@ -553,16 +692,16 @@
    }
 
    /**
-    * Extracts the default value for the preference from a {@link DefaultPreferences} POJO.
+    * Extracts the default value for the preference from a {@link CatPreferences} POJO.
     * 
-    * @param defaultPreference the {@link Preference} to extract a default value for.
+    * @param defaultPreferencesPojo the {@link Preference} to extract a default value for.
     * @return an {@link Optional} containing the the default value for the preference when it is present in
     * <code>defaultPreference</code>; otherwise, an empty {@link Optional}.
     */
 
-   public Optional<String> getDefault(DefaultPreferences defaultPreference) {
-      String defaultValue = this.defaultPreferenceValueExtractor.apply(defaultPreference);
-      if (Objects.nonNull(defaultValue) && this.validateDefaultValue(defaultValue)) {
+   public Optional<String> getDefault(CatPreferences defaultPreferencesPojo) {
+      String defaultValue = this.preferencePojoValueGetter.apply(defaultPreferencesPojo);
+      if (Objects.nonNull(defaultValue) && this.validateValue(defaultValue, IsDefault.YES)) {
          return Optional.of(defaultValue);
       }
       return Optional.empty();
@@ -589,10 +728,22 @@
    }
 
    /**
+    * Predicate to determine if the preference's field editor appears on the specified <code>page</code>.
+    * 
+    * @param page the {@link PreferencePage} to be tested.
+    * @return <code>true</code> when the preference appears on the specified <code>page</code>; otherwise,
+    * <code>null</code>.
+    */
+
+   public boolean isOnPage(PreferencePage page) {
+      return this.page == page;
+   }
+
+   /**
     * Sets the {@link String} value of the preference.
     * 
     * @param value the value to be set.
-    * @throws CatPluginException when unable to set the preference value in the preference store.
+    * @throws CatPluginUserException when unable to set the preference value in the preference store.
     */
 
    public void set(String value) {
@@ -601,23 +752,32 @@
          
          CatPlugin
             .getInstancePreferenceStore()
-            .setValue(this.getPreferenceStoreName(),value);
+            .putValue(this.getPreferenceStoreName(),value);
          
       } catch( Exception e ) {
          
          throw
             new CatPluginException
                    (
-                      StatusManager.BLOCK | StatusManager.LOG,
-                      "CAT Plugin Preference Error",
-                      IStatus.ERROR,
-                        "Unable to get preference."   + "\n"
-                      + "Preference: " + this.name()  + "\n"
-                      + "Value:      " + value        + "\n",
+                      CatErrorCode.InternalError,
+                        "Unable to get preference from preference store." + "\n"
+                      + "Preference: " + this.name()                      + "\n"
+                      + "Value:      " + value                            + "\n",
                       e
                    );
       }
       //@formatter:on
-   };
+   }
+
+   /**
+    * Determines if the preference value is valid.
+    * 
+    * @param value the value for the preference to be tested.
+    * @param isDefault when {@link IsDefault#YES YES} error messages will be formatted for a default value setting
+    * instead of a user selected value.
+    * @return <code>true</code> when the preference value OK; otherwise, <code>false</code>.
+    */
+
+   abstract boolean validateValue(String value, IsDefault isDefault);;
 
 }
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/PreferenceInitializer.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/PreferenceInitializer.java
index e870123..23472a9 100644
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/PreferenceInitializer.java
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/PreferenceInitializer.java
@@ -12,22 +12,12 @@
 
 package org.eclipse.ote.cat.plugin.preferencepage;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import java.io.File;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Objects;
-import org.eclipse.core.runtime.IConfigurationElement;
-import org.eclipse.core.runtime.IExtension;
-import org.eclipse.core.runtime.IExtensionPoint;
-import org.eclipse.core.runtime.IExtensionRegistry;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.ote.cat.plugin.CatPlugin;
-import org.eclipse.ote.cat.plugin.CatPluginException;
-import org.eclipse.ui.statushandlers.StatusManager;
+import org.eclipse.ote.cat.plugin.exception.CatErrorCode;
+import org.eclipse.ote.cat.plugin.exception.CatPluginException;
 
 /**
  * An extension of the {@link AbstractPreferenceInitializer} used to provide default preference values from a JSON file
@@ -40,36 +30,11 @@
 public class PreferenceInitializer extends AbstractPreferenceInitializer {
 
    /**
-    * The name of the attribute in the OSGi Configuration Element that specifies the name of the command line option for
-    * the default preferences file.
-    */
-
-   private static final String commandLineOptionNameAttribute = "option";
-
-   /**
-    * The name of the OSGi Configuration Element that specifies this class as the default value initializer.
-    */
-
-   private static final String configurationElementName = "initializer";
-
-   /**
-    * The dialog box title used for status dialogs.
-    */
-
-   private static final String exceptionTitle = "CAT Plugin Default Preferences";
-
-   /**
-    * The name of the OSGi Extension Point implemented by this class.
-    */
-
-   private static final String extensionPointName = "org.eclipse.core.runtime.preferences";
-
-   /**
     * Searches the application command line options for one starting with the string <code>commandLineOptionName</code>.
     * 
     * @param commandLineOptionName the name of the command line option to search for.
     * @return the first found matching command line option.
-    * @throws CatPluginException when the command line option is not present.
+    * @throws CatPluginUserException when the command line option is not present.
     */
 
    private static String getCommandLineOption(String commandLineOptionName) {
@@ -90,186 +55,21 @@
       CatPluginException commandLineOptionNotPresent =
          new CatPluginException
                 (
-                   StatusManager.LOG,
-                   PreferenceInitializer.exceptionTitle,
-                   IStatus.INFO,
+                   CatErrorCode.NoDefaultPreferencesWarning,
                      "The command line option \"" + commandLineOptionName + "\" was not specified." + "\n"
-                   + "Default preference values for the CAT Plugin were not loaded."                + "\n",
-                   null
+                   + "Default preference values for the CAT Plugin were not loaded."                + "\n"
                 );
       //@formatter:on
       throw commandLineOptionNotPresent;
    }
 
    /**
-    * Obtains the default preferences file command line option name from the CAT Plugin &quot;plugin.xml&quot; file. The
-    * command line option is specified by the {@value #commandLineOptionNameAttribute} attribute of the Configuration
-    * Element {@value #configurationElementName} of the Extension element for the Extension Point
-    * {@value #extensionPointName}.
-    * 
-    * @return the command line option name for the CAT Plugin default preferences file.
-    * @throws CatPluginException when unable to determine the command line option name.
-    */
-
-   private static String getCommandLineOptionName() {
-
-      String defaultPreferenceInitializerExtensionIdentifier =
-         CatPlugin.getDefaultPreferenceInitializerExtensionIdentifier();
-
-      IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
-
-      if (Objects.isNull(extensionRegistry)) {
-         //@formatter:off
-         CatPluginException osgiExtensionRegistryNotAvailable =
-            new CatPluginException
-                   (
-                      StatusManager.LOG,
-                      PreferenceInitializer.exceptionTitle,
-                      IStatus.ERROR,
-                        "The OSGi Extension Registry is not available. Unable to determine the command line option" + "\n"
-                      + "name for the CAT Plugin default preferences file."                                         + "\n",
-                      null
-                   );
-         //@formatter:on
-         throw osgiExtensionRegistryNotAvailable;
-      }
-
-      IExtensionPoint extensionPoint = extensionRegistry.getExtensionPoint(PreferenceInitializer.extensionPointName);
-
-      if (Objects.isNull(extensionPoint)) {
-         //@formatter:off
-         CatPluginException osgiExtensionPointNotFound =
-            new CatPluginException
-                   (
-                      StatusManager.LOG,
-                      PreferenceInitializer.exceptionTitle,
-                      IStatus.ERROR,
-                        "The OSGi Extension Point \"" + PreferenceInitializer.extensionPointName + "\" was not found."  + "\n" 
-                      + "Unable to determine the command line option name for the CAT Plugin default preferences file." + "\n",
-                      null
-                   );
-         //@formatter:on
-         throw osgiExtensionPointNotFound;
-      }
-
-      IExtension extension = null;
-      Exception extensionCause = null;
-
-      try {
-         extension = extensionPoint.getExtension(defaultPreferenceInitializerExtensionIdentifier);
-      } catch (Exception e) {
-         extensionCause = e;
-      }
-
-      if (Objects.isNull(extension)) {
-         //@formatter:off
-         CatPluginException osgiExtensionNotFound =
-            new CatPluginException
-                   (
-                      StatusManager.LOG,
-                      PreferenceInitializer.exceptionTitle,
-                      IStatus.ERROR,
-                        "The OSGi Extension \"" + defaultPreferenceInitializerExtensionIdentifier + "\" was not found."  + "\n" 
-                      + "Unable to determine the command line option name for the CAT Plugin default preferences file."  + "\n",
-                      extensionCause // <- might be null
-                   );
-         //@formatter:on
-         throw osgiExtensionNotFound;
-      }
-
-      IConfigurationElement[] configurationElements = null;
-      Exception configurationElementsCause = null;
-
-      try {
-         configurationElements = extension.getConfigurationElements();
-      } catch (Exception e) {
-         configurationElementsCause = e;
-      }
-
-      if (Objects.isNull(extension)) {
-         //@formatter:off
-         CatPluginException osgiConfigurationElementsNotFound =
-            new CatPluginException
-                   (
-                      StatusManager.LOG,
-                      PreferenceInitializer.exceptionTitle,
-                      IStatus.ERROR,
-                        "The OSGi Configuration Elments of the Extension \"" + defaultPreferenceInitializerExtensionIdentifier + "\""    + "\n" 
-                      + "were not found. Unable to determine the command line option name for the CAT Plugin default preferences file."  + "\n",
-                      configurationElementsCause
-                   );
-         //@formatter:on
-         throw osgiConfigurationElementsNotFound;
-      }
-
-      IConfigurationElement configurationElement = null;
-      Exception configurationElementCause = null;
-
-      try {
-         for (int i = 0; i < configurationElements.length; i++) {
-            if (Objects.nonNull(configurationElements[i]) && PreferenceInitializer.configurationElementName.equals(
-               configurationElements[i].getName())) {
-               configurationElement = configurationElements[i];
-               break;
-            }
-         }
-      } catch (Exception e) {
-         configurationElementCause = e;
-      }
-
-      if (Objects.isNull(configurationElement)) {
-         //@formatter:off
-         CatPluginException cannotFindInitializerConfigurationElement =
-            new CatPluginException
-                   (
-                      StatusManager.LOG,
-                      PreferenceInitializer.exceptionTitle,
-                      IStatus.INFO,
-                        "The command line option used to specify the default preference values file cannot be determined."    + "\n"
-                      + "Ensure the \"initializer\" element for the extension point \"org.eclipse.core.runtime.preferences\"" + "\n"
-                      + "is specified in the \"plugin.xml\" file of the CAT Plugin."                                          + "\n",
-                      configurationElementCause // <- might be null
-                   );
-         //@formatter:on
-         throw cannotFindInitializerConfigurationElement;
-      }
-
-      String option = null;
-      Exception optionCause = null;
-
-      try {
-         option = configurationElement.getAttribute(PreferenceInitializer.commandLineOptionNameAttribute);
-      } catch (Exception e) {
-         optionCause = e;
-      }
-
-      if (Objects.isNull(option)) {
-         //@formatter:off
-         CatPluginException cannotDetermineCommandLineOptionNameForDefaults =
-            new CatPluginException
-                   (
-                      StatusManager.LOG,
-                      PreferenceInitializer.exceptionTitle,
-                      IStatus.INFO,
-                        "The command line option used to specify the default preference values file cannot be determined." + "\n"
-                      + "Ensure the attribute \"option\" of the \"initializer\" element for the extension point"           + "\n" 
-                      + "\"org.eclipse.core.runtime.preferences\" is set in the \"plugin.xml\" file of the CAT Plugin."    + "\n",
-                      optionCause // <- might be null
-                   );
-         //@formatter:on
-         throw cannotDetermineCommandLineOptionNameForDefaults;
-      }
-
-      return option;
-   }
-
-   /**
     * Parses the default preferences file path from the command line option.
     * 
     * @param commandLineOptionName the name of the command line option.
     * @param commandLineOption the command line option and value from the application.
     * @return the command line option value
-    * @throws CatPluginException when unable to parse the value from the <code>commandLineOption</code>.
+    * @throws CatPluginUserException when unable to parse the value from the <code>commandLineOption</code>.
     */
 
    private static String getCommandLineOptionValue(String commandLineOptionName, String commandLineOption) {
@@ -282,11 +82,8 @@
          CatPluginException noOptionValueException =
             new CatPluginException
                    (
-                      StatusManager.LOG,
-                      PreferenceInitializer.exceptionTitle,
-                      IStatus.WARNING,
-                      "A value was not specified for the command line option \"" + commandLineOptionName + "\"." + "\n",
-                      null
+                      CatErrorCode.CommandLineOptionError,
+                      "A value was not specified for the command line option \"" + commandLineOptionName + "\"." + "\n"
                    );
          //@formatter:on
          throw noOptionValueException;
@@ -312,9 +109,9 @@
 
    /**
     * When the default preferences file command line option is present and the specified JSON file is successfully
-    * parsed, the CAT Plugin preference store default values are set with those from the default preferences file.
+    * parsed, the CAT Plug-In preference store default values are set with those from the default preferences file.
     * 
-    * @throws CatPluginException when:
+    * @throws CatPluginUserException when:
     * <ul>
     * <li>unable to determine the name of the default preferences file command line option,</li>
     * <li>unable to parse the command line option,</li>
@@ -323,7 +120,7 @@
     * <li>a default preference cannot be validated.</li>
     * </ul>
     * @implNote This method is only called by the Eclipse framework when a default value is requested from the CAT
-    * Plugin preference store.
+    * Plug-In preference store.
     */
 
    @Override
@@ -333,93 +130,19 @@
       String commandLineOption = null;
       String commandLineOptionValue = null;
       try {
-         commandLineOptionName = PreferenceInitializer.getCommandLineOptionName();
+         commandLineOptionName = CatPlugin.getDefaultPreferencesCommandLineOptionName();
          commandLineOption = PreferenceInitializer.getCommandLineOption(commandLineOptionName);
          commandLineOptionValue =
             PreferenceInitializer.getCommandLineOptionValue(commandLineOptionName, commandLineOption);
 
-         File file = null;
-         Exception fileException = null;
-
-         try {
-
-            Path filePath = Paths.get(commandLineOptionValue);
-            file = filePath.toFile();
-
-            if (!file.canRead()) {
-            //@formatter:off
-            fileException =
-               new CatPluginException
-                      (
-                         StatusManager.BLOCK | StatusManager.LOG,
-                         PreferenceInitializer.exceptionTitle,
-                         IStatus.ERROR,
-                           "The CAT Plugin default preferences file does not exsit or cannot be read." + "\n"
-                         + "   Default Preferences File: " + commandLineOptionValue                    + "\n"
-                         + "   Command Line Option:      " + commandLineOptionName                     + "\n",
-                         null
-                      );
-            //@formatter:on
-            }
-
-         } catch (Exception e) {
-            fileException = e;
-         }
-
-         if (Objects.nonNull(fileException)) {
-            if (fileException instanceof CatPluginException) {
-               throw fileException;
-            } else {
-            //@formatter:off
-            CatPluginException systemFileException =
-               new CatPluginException
-                      (
-                         StatusManager.BLOCK | StatusManager.LOG,
-                         PreferenceInitializer.exceptionTitle,
-                         IStatus.ERROR,
-                           "An error occurred testing the accessability of the CAT Plugin default preferences file." + "\n"
-                         + "   Default Preferences File: " + commandLineOptionValue                                  + "\n"
-                         + "   Command Line Option:      " + commandLineOptionName                                   + "\n",
-                         fileException
-                      );
-            //@formatter:on
-               throw systemFileException;
-            }
-         }
-
-         DefaultPreferences defaultPreferences = null;
-         Exception defaultPreferencesException = null;
-
-         try {
-            ObjectMapper objectMapper = new ObjectMapper();
-            defaultPreferences = objectMapper.readValue(file, DefaultPreferences.class);
-         } catch (Exception e) {
-            defaultPreferencesException = null;
-         }
-
-         if (Objects.isNull(defaultPreferences)) {
-         //@formatter:off
-         CatPluginException failedToReadDefaultPreferencesException =
-            new CatPluginException
-                   (
-                      StatusManager.BLOCK | StatusManager.LOG,
-                      PreferenceInitializer.exceptionTitle,
-                      IStatus.WARNING,
-                        "Failed to read the CAT Plugin default preferences file." + "\n"
-                      + "   Default Preferences File: " + commandLineOptionValue  + "\n"
-                      + "   Command Line Option:      " + commandLineOptionName   + "\n",
-                      defaultPreferencesException // <- might be null
-                   );
-         //@formatter:on
-            throw failedToReadDefaultPreferencesException;
-         }
+         CatPreferences catPreferences = CatPreferences.read(commandLineOptionValue);
 
          IPreferenceStore preferenceStore = CatPlugin.getInstancePreferenceStore();
 
          for (Preference preference : Preference.values()) {
             //@formatter:off
             preference
-               .getDefault( defaultPreferences )
+               .getDefault( catPreferences )
                .ifPresent
                   ( 
                      ( defaultValue ) -> preferenceStore.setDefault
@@ -438,9 +161,7 @@
          CatPluginException setDefaultValuesException =
             new CatPluginException
                    (
-                      StatusManager.BLOCK | StatusManager.LOG,
-                      PreferenceInitializer.exceptionTitle,
-                      IStatus.ERROR,
+                      CatErrorCode.PreferenceFileError,
                         "Failed to set a CAT Plugin default preference value."    + "\n"
                       + "   Default Preferences File: " + commandLineOptionValue  + "\n"
                       + "   Command Line Option:      " + commandLineOptionName   + "\n",
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/PreferencePage.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/PreferencePage.java
index c6389af..2165ca8 100644
--- a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/PreferencePage.java
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/preferencepage/PreferencePage.java
@@ -20,7 +20,7 @@
  * @see
  * <ul>
  * <li>{@link PreferencePage#CAT_SETTINGS CAT_SETTINGS}</li>
- * <li>{@link PreferencesPage#PLE_CONFIGURATION_CACHE PLE_CONFIGURATION_CACHE}</li>
+ * <li>{@link PreferencePage#PLE_CONFIGURATION_LOADER PLE_CONFIGURATION_LOADER}</li>
  * </ul>
  */
 
@@ -37,7 +37,7 @@
     * Preference sub-page for loading PLE Configurations from an OPLE server.
     */
 
-   PLE_CONFIGURATION_CACHE("PLE Configuration Cache Settings And Loader");
+   PLE_CONFIGURATION_LOADER("PLE Configuration Loader");
 
    /**
     * Save the page title used for the preference page.
@@ -46,7 +46,7 @@
    private String pageTitle;
 
    /**
-    * Create a new {@Link PreferncePage} enumeration member.
+    * Create a new {@link PreferencePage} enumeration member.
     * 
     * @param pageTitle the title of the preference page represented by the enumeration member.
     */
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatNature.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatNature.java
new file mode 100644
index 0000000..5ea4cc2
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatNature.java
@@ -0,0 +1,113 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.project;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectNature;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.ote.cat.plugin.CatPlugin;
+import org.eclipse.ote.cat.plugin.exception.CatErrorCode;
+import org.eclipse.ote.cat.plugin.exception.CatPluginException;
+import org.eclipse.ote.cat.plugin.preferencepage.Preference;
+
+/**
+ * An implementation of the {@link IProjectNature} interface used to configure and deconfigure projects for building
+ * with the CAT annotation processor.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class CatNature implements IProjectNature {
+
+   /**
+    * Saves a reference to the project this {@link CatNature} instance is applied to.
+    */
+
+   private IProject project;
+
+   /**
+    * The nature configure method is used to apply the Java compiler options needed for the CAT annotation processor.
+    * <p>
+    * {@inheritDoc}
+    */
+
+   @Override
+   public void configure() throws CoreException {
+      try {
+         CatParameters catParameters = new CatParameters();
+         CatProject catProject = new CatProject(this.project, catParameters);
+         CatProjectManager catProjectManager = CatPlugin.getCatProjectManager();
+         catProjectManager.addCatProject(catProject);
+         catProject.configure();
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException catNatureConfigurationException =
+            new CatPluginException
+                   (
+                      CatErrorCode.InternalError,
+                        "Failed to configure project for the CAT annotation processor." + "\n"
+                      + "   Project: " + this.project.toString()                        + "\n",
+                      e
+                   );
+         //@formatter:on
+         catNatureConfigurationException.log();
+      }
+   }
+
+   /**
+    * The nature deconfigure method is used to remove the Java compiler options for the CAT annotation processor from
+    * the project.
+    * <p>
+    * {@inheritDoc}
+    */
+
+   @Override
+   public void deconfigure() throws CoreException {
+      //@formatter:off
+      CatPlugin
+         .getCatProjectManager()
+         .removeCatProject(this.project)
+         .ifPresent( CatProject::deconfigure );
+
+      Preference.JTS_PROJECTS.set
+         (
+            Arrays
+               .stream( Preference.JTS_PROJECTS.get().split( "," ) )
+               .filter( ( jtsProject ) -> !jtsProject.equals( this.project.toString() ) )
+               .collect( Collectors.joining( "," ) )
+         );
+      //@formatter:on
+
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+
+   @Override
+   public IProject getProject() {
+      return this.project;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+
+   @Override
+   public void setProject(IProject project) {
+      this.project = project;
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatParameters.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatParameters.java
new file mode 100644
index 0000000..d65be18
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatParameters.java
@@ -0,0 +1,124 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.project;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.eclipse.ote.cat.plugin.Constants;
+import org.eclipse.ote.cat.plugin.preferencepage.Preference;
+
+/**
+ * A container class for the CAT Plug-In preferences needed to configure the compiler options for a project.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class CatParameters {
+
+   /**
+    * Gets the CAT annotation processor option name used to specify the path of the PLE Configuration file.
+    * 
+    * @return the PLE Configuration path option name.
+    */
+
+   public static String getPleConfigurationPathKey() {
+      return Constants.pleConfigurationPathCatOption;
+   }
+
+   /**
+    * Gets the CAT annotation processor option name used to specify the source location method.
+    * 
+    * @return the source location method option name.
+    */
+
+   public static String getSourceLocationMethodKey() {
+      return Constants.sourceLocationMethodCatOption;
+   }
+
+   /**
+    * Save the path to the CAT annotation processor Jar file. This parameter is used to configure the project's factory
+    * path.
+    */
+
+   private final Path catJarPath;
+
+   /**
+    * Saves the path to the PLE Configuration file to be used by the CAT annotation processor. This parameter is used to
+    * configure the project's annotation processor parameters.
+    */
+
+   private final Path pleConfigurationPath;
+
+   /**
+    * Saves the method to be used by the CAT annotation processor to locate source files. This parameter is used to
+    * configure the project's annotation processor parameters.
+    */
+
+   private final String sourceLocationMethod;
+
+   /**
+    * Creates a new immutable {@link CatParameters} object with the preference values needed to configure the compiler
+    * options for a project.
+    * <h3>See Also:</h3>
+    * <ul>
+    * <li>{@link Preference#CAT_JAR}</li>
+    * <li>{@link Preference#SOURCE_LOCATION_METHOD}</li>
+    * <li>{@link Preference#PLE_CONFIGURATION_CACHE_FOLDER}</li>
+    * <li>{@link Preference#PLE_CONFIGURATION}</li>
+    * </ul>
+    */
+
+   public CatParameters() {
+
+      String catJarPreference = Preference.CAT_JAR.get();
+      this.catJarPath = Paths.get(catJarPreference);
+
+      this.sourceLocationMethod = Preference.SOURCE_LOCATION_METHOD.get();
+
+      String pleConfigurationCacheFolderPreference = Preference.PLE_CONFIGURATION_CACHE_FOLDER.get();
+      String pleConfigurationPreference = Preference.PLE_CONFIGURATION.get();
+
+      this.pleConfigurationPath = Paths.get(pleConfigurationCacheFolderPreference, pleConfigurationPreference);
+   }
+
+   /**
+    * Gets the path to the CAT annotation processor Jar file.
+    * 
+    * @return path to the CAT Jar file.
+    */
+
+   public Path getCatJarPath() {
+      return this.catJarPath;
+   }
+
+   /**
+    * Gets the path to the PLE Configuration file to be used by the CAT annotation processor.
+    * 
+    * @return the PLE Configuration file path.
+    */
+
+   public Path getPleConfigurationPath() {
+      return this.pleConfigurationPath;
+   }
+
+   /**
+    * Get the name of the CAT annotation processor method used locate source files.
+    * 
+    * @return the source location method name.
+    */
+
+   public String getSourceLocationMethod() {
+      return this.sourceLocationMethod;
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatProject.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatProject.java
new file mode 100644
index 0000000..460f54d
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatProject.java
@@ -0,0 +1,464 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.project;
+
+import java.nio.file.Path;
+import java.util.Objects;
+import java.util.Optional;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jdt.apt.core.util.AptConfig;
+import org.eclipse.jdt.apt.core.util.IFactoryPath;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.ote.cat.plugin.CatPlugin;
+import org.eclipse.ote.cat.plugin.Constants;
+import org.eclipse.ote.cat.plugin.exception.CatErrorCode;
+import org.eclipse.ote.cat.plugin.exception.CatPluginException;
+
+/**
+ * A CAT project represents a view of a project resource with a Java project nature that is configured for building with
+ * the CAT annotation processor. The {@link CatProject} maintains the CAT annotation processor settings so the Java
+ * project can be reconfigured or deconfigured.
+ * 
+ * @author Loren K. Ashley
+ * @implNote The {@link CatProject} is implemented as an immutable object. The CAT annotation processor parameters are
+ * also contained within an immutable object. When changes are made to the CAT annotation processor settings a new
+ * {@link CatProject} is created and the original {@link CatProject} object is replaced in the
+ * {@link CatProjectManager}.
+ */
+
+public class CatProject {
+
+   /**
+    * Save a reference to the Eclipse project resource with the {@link CatNature} this {@link CatProject} represents.
+    */
+
+   private final IProject project;
+
+   /**
+    * Saves the CAT annotation processor parameters. This member is read from and saved in the
+    * {@value Constants#catProjectInfoFileName} file in the project's root directory.
+    */
+
+   private final CatProjectInfo catProjectInfo;
+
+   /**
+    * Creates a new {@link CatProject} for the <code>project</code> with the CAT annotation processor parameters from
+    * the <code>catParameters</code>.
+    * 
+    * @param project the {@link IProject} resource with a {@link CatNature} to be wrapped.
+    * @param catParameters the CAT preferences the CAT annotation processor parameters are derived from.
+    */
+
+   public CatProject(IProject project, CatParameters catParameters) {
+      this.project = project;
+      this.catProjectInfo = new CatProjectInfo(project, catParameters);
+   }
+
+   /**
+    * Creates a new {@link CatProject} for the <code>project</code> with the CAT annotation processor parameters from
+    * the <code>catParameters</code>.
+    * 
+    * @param project the {@link IProject} resource with a {@link CatNature} to be wrapped.
+    * @param catProjectInfo the CAT annotation processor parameters.
+    */
+
+   public CatProject(IProject project, CatProjectInfo catProjectInfo) {
+      this.project = project;
+      this.catProjectInfo = catProjectInfo;
+   }
+
+   /**
+    * Creates a new {@link CatProject} without a {@link IProject} reference with the CAT annotation processor parameters
+    * from <code>catProjectInfo</code>.
+    * 
+    * @param catProjectInfo the CAT annotation processor parameters.
+    * @implNote This constructor is used when creating {@link CatProject}s from the CAT Plug-In state file. A
+    * {@link CatProjectManager} background task will associate the {@link CatProject} with an Eclipse {@link IProject}.
+    * If the CAT project referenced by the <code>catProjectInfo</code> object is not found, the {@link CatProject} will
+    * be removed from the {@link CatProjectManager}.
+    */
+
+   public CatProject(CatProjectInfo catProjectInfo) {
+      this.project = null;
+      this.catProjectInfo = catProjectInfo;
+   }
+
+   /**
+    * Creates a new {@link CatProject} for the <code>project</code> with the {@link CatProjectInfo} CAT annotation
+    * processor parameters read from the <code>project</code>'s {@value Constants#catProjectInfoFileName} file.
+    * 
+    * @param project the Eclipse {@link IProject} resource to be wrapped.
+    * @return a new {@link CatProject} wrapping the <code>project</code> with the CAT annotation parameters read from
+    * the {@value Constants#catProjectInfoFileName}.
+    */
+
+   public static CatProject create(IProject project) {
+      CatProjectInfo catProjectInfo = CatProjectInfo.read(project);
+      return new CatProject(project, catProjectInfo);
+   }
+
+   /**
+    * &quot;Updates&quot; the <code>catProject</code> with the {@link CatProjectInfo} read from the file
+    * {@value Constants#catProjectInfoFileName} by creating a new {@link CatProject} when the read
+    * {@link CatProjectInfo} is different than the {@link CatProjectInfo} in the <code>catProject</code>.
+    * 
+    * @param catProject the {@link CatProject} to update.
+    * @param project the {@link IProject} expected to be wrapped by the {@link CatProject}.
+    * @return an {@link Optional} containing the updated {@link CatProject} when the read {@link CatProjectInfo} is
+    * different; otherwise, an empty {@link Optional}.
+    */
+
+   public static Optional<CatProject> updateCatProject(CatProject catProject, IProject project) {
+
+      CatProjectInfo catProjectInfo = CatProjectInfo.read(project);
+      //@formatter:off
+      return
+            Objects.nonNull( catProject.catProjectInfo )
+         && catProject.catProjectInfo.equals( catProjectInfo )
+         && Objects.nonNull( catProject.project )
+         && catProject.project.equals( project )
+         ? Optional.empty()
+         : Optional.of( new CatProject( project, catProjectInfo ) );
+      //@formatter:on
+   }
+
+   /**
+    * Deletes the {@value Constants#catProjectInfoFileName} file when a {@link CatProject} is being deconfigured.
+    * 
+    * @throws CatPluginException when unable to delete the {@value Constants#catProjectInfoFileName} file.
+    */
+
+   private void removeCatProjectInfo() {
+      IProject project = this.getProject();
+      IFile iFile = project.getFile(Constants.catProjectInfoFileName);
+      try {
+         iFile.delete(true, null);
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException catProjectInfoFileException =
+            new CatPluginException
+                   (
+                      CatErrorCode.CatProjectInfoFileError,
+                        "Unable to delete the \"" + Constants.catProjectInfoFileName + "\" file." + "\n"
+                      + "   Project: " + this.project.toString()                                  + "\n",
+                      e
+                   );
+         //@formatter:on
+         throw catProjectInfoFileException;
+      }
+   }
+
+   /**
+    * Gets the CAT annotation processor parameters for the {@link CatProject}.
+    * 
+    * @return the CAT annotation processor parameters.
+    */
+
+   public CatProjectInfo getCatProjectInfo() {
+      return this.catProjectInfo;
+   }
+
+   /**
+    * Gets the Eclipse {@link IProject} resource wrapped by the {@link CatProject}.
+    * 
+    * @return the {@link IProject} for the {@link CatProject}.
+    */
+
+   public IProject getProject() {
+      return this.project;
+   }
+
+   /**
+    * Updates the CAT annotation processor Jar file path for the {@link CatProject}. When <code>newCatJarPath</code> and
+    * <code>oldCatJarPath</code> are the same no changes to the project are made.
+    * 
+    * @param catProjectInfo when <code>newCatJarPath</code> is non-<code>null</code> it is set in the
+    * {@link CatProjectInfo} object.
+    * @param newCatJarPath the new Jar file path. Set this parameter to <code>null</code> when the Jar file path is to
+    * be removed from the Java project's {@link IFactoryPath}.
+    * @param oldCatJarPath the original Jar file path. Set this parameter to <code>null</code> when configuring a
+    * project for the first time.
+    * @throws CatPluginException when unable to update the {@link IJavaProject}'s {@link IFactoryPath}.
+    */
+
+   private void updateFactoryPath(CatProjectInfo catProjectInfo, Path newCatJarPath, Path oldCatJarPath) {
+
+      try {
+
+         if (Objects.nonNull(newCatJarPath)) {
+
+            catProjectInfo.setCatJarPath(newCatJarPath);
+
+            if (newCatJarPath.equals(oldCatJarPath)) {
+               return;
+            }
+         }
+
+         IJavaProject javaProject = JavaCore.create(this.project);
+
+         boolean factoryPathChanged = false;
+         IFactoryPath factoryPath = AptConfig.getFactoryPath(javaProject);
+
+         if (Objects.nonNull(oldCatJarPath)) {
+            factoryPath.removeExternalJar(oldCatJarPath.toFile());
+            factoryPathChanged = true;
+         }
+
+         if (Objects.nonNull(newCatJarPath)) {
+            factoryPath.addExternalJar(newCatJarPath.toFile());
+            factoryPathChanged = true;
+         }
+
+         if (factoryPathChanged) {
+            AptConfig.setFactoryPath(javaProject, factoryPath);
+         }
+
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException updateFactoryPathException =
+            new CatPluginException
+                   (
+                      CatErrorCode.InternalError,
+                        "Failed to update the underlying Java project's Factory Path." + "\n"
+                      + "   Project:          " + this.project.toString()              + "\n"
+                      + "   New CAT Jar Path: " + newCatJarPath                        + "\n"
+                      + "   Old CAT Jar Path: " + oldCatJarPath                        + "\n",
+                      e
+                   );
+         //@formatter:on
+         throw updateFactoryPathException;
+      }
+   }
+
+   /**
+    * Updates the CAT annotation processor Source Location Method option parameter the {@link CatProject}. When
+    * <code>newSourceLocationMethod</code> and <code>oldSourceLocationMethod</code> are the same no changes to the
+    * project are made.
+    * 
+    * @param catProjectInfo when <code>newSourceLocationMethod</code> is non-<code>null</code> it is set in the
+    * {@link CatProjectInfo} object.
+    * @param newSourceLocationMethod the new Source Location Method. Set this parameter to <code>null</code> when CAT
+    * annotation processor option is to be removed from the Java project's annotation processor options.
+    * @param oldSourceLocationMethod the original Source Location Method. Set this parameter to <code>null</code> when
+    * configuring a project for the first time.
+    * @throws CatPluginException when unable to update the {@link IJavaProject}'s annotation processor options.
+    */
+
+   private void updateSourceLocationMethod(CatProjectInfo catProjectInfo, String newSourceLocationMethod, String oldSourceLocationMethod) {
+
+      try {
+
+         if (Objects.nonNull(newSourceLocationMethod)) {
+
+            catProjectInfo.setSourceLocationMethod(oldSourceLocationMethod);
+
+            if (newSourceLocationMethod.equals(oldSourceLocationMethod)) {
+               return;
+            }
+         }
+
+         IJavaProject javaProject = JavaCore.create(this.project);
+
+         if (Objects.nonNull(oldSourceLocationMethod)) {
+            AptConfig.removeProcessorOption(javaProject, CatParameters.getSourceLocationMethodKey());
+         }
+
+         if (Objects.nonNull(newSourceLocationMethod)) {
+            AptConfig.addProcessorOption(javaProject, CatParameters.getSourceLocationMethodKey(),
+               newSourceLocationMethod);
+         }
+
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException updateSourceLocationMethodException =
+            new CatPluginException
+                   (
+                      CatErrorCode.InternalError,
+                        "Failed to update the underlying Java project's annotation processor options" + "\n"
+                      + "   Project:                    " + this.project.toString()                   + "\n"
+                      + "   New Source Location Method: " + newSourceLocationMethod                   + "\n"
+                      + "   Old Source Lcoation Method: " + oldSourceLocationMethod                   + "\n",
+                      e
+                   );
+         //@formatter:on
+         throw updateSourceLocationMethodException;
+      }
+   }
+
+   /**
+    * Updates the CAT annotation processor PLE Configuration Path option parameter the {@link CatProject}. When
+    * <code>newPleConfigurationPath</code> and <code>oldPleConfigurationPath</code> are the same no changes to the
+    * project are made.
+    * 
+    * @param catProjectInfo when <code>newPleConfigurationPath</code> is non-<code>null</code> it is set in the
+    * {@link CatProjectInfo} object.
+    * @param newPleConfigurationPath the new PLE Configuration . Set this parameter to <code>null</code> when CAT
+    * annotation processor option is to be removed from the Java project's annotation processor options.
+    * @param oldPleConfigurationPath the original Source Location Method. Set this parameter to <code>null</code> when
+    * configuring a project for the first time.
+    * @throws CatPluginException when unable to update the {@link IJavaProject}'s annotation processor options.
+    */
+
+   private void updatePleConfigurationPath(CatProjectInfo catProjectInfo, Path newPleConfigurationPath, Path oldPleConfigurationPath) {
+
+      try {
+
+         if (Objects.nonNull(newPleConfigurationPath)) {
+
+            catProjectInfo.setPleConfigurationPath(oldPleConfigurationPath);
+
+            if (newPleConfigurationPath.equals(oldPleConfigurationPath)) {
+               return;
+            }
+         }
+
+         IJavaProject javaProject = JavaCore.create(this.project);
+
+         if (Objects.nonNull(oldPleConfigurationPath)) {
+            AptConfig.removeProcessorOption(javaProject, CatParameters.getPleConfigurationPathKey());
+         }
+
+         if (Objects.nonNull(newPleConfigurationPath)) {
+            AptConfig.addProcessorOption(javaProject, CatParameters.getPleConfigurationPathKey(),
+               newPleConfigurationPath.toString());
+         }
+
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException updatePleConfigurationPathException =
+            new CatPluginException
+                   (
+                      CatErrorCode.InternalError,
+                        "Failed to update the underlying Java project's annotation processor options" + "\n"
+                      + "   Project:                    " + this.project.toString()                   + "\n"
+                      + "   New PLE Configuration Path: " + newPleConfigurationPath                   + "\n"
+                      + "   Old PLE Configuration Path: " + oldPleConfigurationPath                   + "\n",
+                      e
+                   );
+         //@formatter:on
+         throw updatePleConfigurationPathException;
+      }
+   }
+
+   /**
+    * Updates the {@link CatProject}'s CAT annotation processor parameters from the current values in the CAT Plug-In's
+    * preference store. When a change is made to the CAT project's settings:
+    * <ul>
+    * <li>the new settings are written to the project's {@value Constants#catProjectInfoFileName} file,</li>
+    * <li>this {@link CatProject} object is removed from the {@link CatProjectManager}, and</li>
+    * <li>a new {@link CatProject} object with the new CAT annotation processor parameters is added to the
+    * {@link CatProjectManager}.</li>
+    * </ul>
+    */
+
+   public void update() {
+
+      CatProject newCatProject = null;
+
+      try (CatProjectInfo newCatProjectInfo = new CatProjectInfo()) {
+
+         newCatProjectInfo.setProjectName(this.project.toString());
+         CatParameters catParameters = new CatParameters();
+
+         Path oldCatJarPath = this.catProjectInfo.getCatJarPath();
+         Path newCatJarPath = catParameters.getCatJarPath();
+         this.updateFactoryPath(newCatProjectInfo, newCatJarPath, oldCatJarPath);
+
+         String oldSourceLocationMethod = this.catProjectInfo.getSourceLocationMethod();
+         String newSourceLocationMethod = catParameters.getSourceLocationMethod();
+         this.updateSourceLocationMethod(newCatProjectInfo, newSourceLocationMethod, oldSourceLocationMethod);
+
+         Path oldPleConfigurationPath = this.catProjectInfo.getPleConfigurationPath();
+         Path newPleConfigurationPath = catParameters.getPleConfigurationPath();
+         this.updatePleConfigurationPath(newCatProjectInfo, newPleConfigurationPath, oldPleConfigurationPath);
+
+         if (this.catProjectInfo.equals(newCatProjectInfo)) {
+            return;
+         }
+
+         CatProjectInfo.write(project, newCatProjectInfo);
+         newCatProject = new CatProject(this.project, newCatProjectInfo);
+      }
+
+      if (Objects.nonNull(newCatProject)) {
+         CatPlugin.getCatProjectManager().addCatProject(newCatProject);
+      }
+
+   }
+
+   /**
+    * Configures the {@link CatProject}'s CAT annotation processor parameters from the current values in the CAT
+    * Plug-In's preference store. The CAT project's settings are saved into the
+    * {@value Constants#catProjectInfoFileName} file. This {@link CatProject} is added to the {@link CatProjectManager}.
+    */
+
+   public void configure() {
+
+      CatProject newCatProject = null;
+
+      try (CatProjectInfo newCatProjectInfo = new CatProjectInfo()) {
+
+         Path newCatJarPath = this.catProjectInfo.getCatJarPath();
+         this.updateFactoryPath(newCatProjectInfo, newCatJarPath, null);
+
+         String newSourceLocationMethod = this.catProjectInfo.getSourceLocationMethod();
+         this.updateSourceLocationMethod(newCatProjectInfo, newSourceLocationMethod, null);
+
+         Path newPleConfigurationPath = this.catProjectInfo.getPleConfigurationPath();
+         this.updatePleConfigurationPath(newCatProjectInfo, newPleConfigurationPath, null);
+
+         CatProjectInfo.write(project, newCatProjectInfo);
+         newCatProject = new CatProject(this.project, newCatProjectInfo);
+      }
+
+      if (Objects.nonNull(newCatProject)) {
+         CatPlugin.getCatProjectManager().addCatProject(newCatProject);
+      }
+
+   }
+
+   /**
+    * Removes the {@link CatProject}'s CAT annotation processor parameters from project. The CAT project's settings are
+    * saved into the {@value Constants#catProjectInfoFileName} file is deleted and this {@link CatProject} is removed
+    * from the {@link CatProjectManager}..
+    */
+
+   public void deconfigure() {
+
+      CatProjectInfo newCatProjectInfo = new CatProjectInfo();
+
+      Path oldCatJarPath = this.catProjectInfo.getCatJarPath();
+      this.updateFactoryPath(newCatProjectInfo, null, oldCatJarPath);
+
+      String oldSourceLocationMethod = this.catProjectInfo.getSourceLocationMethod();
+      this.updateSourceLocationMethod(newCatProjectInfo, null, oldSourceLocationMethod);
+
+      Path oldPleConfigurationPath = this.catProjectInfo.getPleConfigurationPath();
+      this.updatePleConfigurationPath(newCatProjectInfo, null, oldPleConfigurationPath);
+
+      this.removeCatProjectInfo();
+      CatPlugin.getCatProjectManager().removeCatProject(this.project);
+
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+
+   public String toString() {
+      return this.catProjectInfo.getProjectName();
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatProjectInfo.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatProjectInfo.java
new file mode 100644
index 0000000..b87965a
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatProjectInfo.java
@@ -0,0 +1,282 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.project;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import java.nio.file.Path;
+import java.util.Objects;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.ote.cat.plugin.Constants;
+import org.eclipse.ote.cat.plugin.exception.CatErrorCode;
+import org.eclipse.ote.cat.plugin.exception.CatPluginException;
+import org.eclipse.ote.cat.plugin.util.JsonFileOperations;
+
+/**
+ * Encapsulates CAT annotation processor parameters for a {@link CatProject}. This class is marshaled to and from the
+ * {@value Constants#catProjectInfoFile} file for a CAT project to persist the CAT annotation processor parameters. The
+ * {@link CatProjectInfo} class implements the {@link AutoCloseable} interface. Once closed a {@link CatProjectInfo}
+ * object becomes immutable and any invocations of setter methods will cause an exception to be thrown.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class CatProjectInfo implements AutoCloseable {
+
+   /**
+    * Saves a single instance of a {@link JsonFileOperations} object specialized for reading and writing
+    * {@link CatProjectInfo} objects to and from JSON files.
+    */
+
+   //@formatter:off
+   private static JsonFileOperations<CatProjectInfo> jsonFileOperations = 
+      new JsonFileOperations<>
+             (
+                CatProjectInfo.class,
+                Constants.catProjectInfoFileDescription
+             );
+   //@formatter:on
+
+   /**
+    * Exception thrown when an attempt is made to modify a {@link CatProjectInfo} object after it has been closed.
+    */
+
+   //@formatter:off
+   private static CatPluginException lockedException =
+      new CatPluginException
+             (
+                CatErrorCode.InternalError,
+                "Attempt to modify a locked (immutable) CatProjectInfo object." + "\n"
+             );
+   //@formatter:on
+
+   /**
+    * Predicate used to compare preference values when one or both values might be <code>null</code>. Two
+    * <code>null</code> values are considered equal. Non-<code>null</code> values are compared according to the equals
+    * method for the type <code>T</code>.
+    * 
+    * @param <T> the type of values being compared.
+    * @param a value to compare.
+    * @param b value to compare.
+    * @return <code>true</code> when the values are equal; otherwise, <code>false</code>.
+    */
+
+   private static <T> boolean equals(T a, T b) {
+
+      if (Objects.nonNull(a) ^ Objects.nonNull(b)) {
+         return false;
+      }
+
+      if (Objects.nonNull(a) && !a.equals(b)) {
+         return false;
+      }
+
+      return true;
+   }
+
+   /**
+    * Reads the {@value Constants#catProjectInfoFileName} file for the <code>project</code>.
+    * 
+    * @param project the {@link IProject} used to locate the {@value Constants#catProjectInfoFileName} file.
+    * @return a {@link CatProjectInfo} object with the preference values read from the file.
+    * @throws CatPluginException when unable to read the file.
+    */
+
+   public static CatProjectInfo read(IProject project) {
+      try {
+         IFile iFile = project.getFile(Constants.catProjectInfoFileName);
+         try (CatProjectInfo catProjectInfo = CatProjectInfo.jsonFileOperations.read(iFile)) {
+            return catProjectInfo;
+         }
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException readCatProjectInfoException =
+            new CatPluginException
+                   (
+                      CatErrorCode.CatProjectInfoFileError,
+                        "Failed to read \"" + Constants.catProjectInfoFileName + "\" file." + "\n"
+                      + "   Project: " + project.toString()                                 + "\n",
+                      e
+                   );
+         //@formatter:on
+         throw readCatProjectInfoException;
+      }
+   }
+
+   /**
+    * Writes the <code>catProjectInfo</code> to the {@value Constants#catProjectInfoFileName} file in the
+    * <code>project</code>.
+    * 
+    * @param project the {@link IProject} to write the {@value Constants#catProjectInfoFileName} file in.
+    * @param catProjectInfo the CAT Plug-In preferences to be saved.
+    * @throws CatPluginException when unable to write the file.
+    */
+
+   public static void write(IProject project, CatProjectInfo catProjectInfo) {
+      try {
+         IFile iFile = project.getFile(Constants.catProjectInfoFileName);
+         CatProjectInfo.jsonFileOperations.write(iFile, catProjectInfo);
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException writeCatProjectInfoException =
+            new CatPluginException
+                   (
+                      CatErrorCode.CatProjectInfoFileError,
+                        "Failed to write \"" + Constants.catProjectInfoFileName + "\" file." + "\n"
+                      + "   Project: " + project.toString()                                  + "\n",
+                      e
+                   );
+         //@formatter:on
+         throw writeCatProjectInfoException;
+      }
+   }
+
+   /**
+    * Saves the path to the CAT annotation processor Jar file.
+    */
+
+   private Path catJarPath;
+
+   /**
+    * Lock flag used to make the {@link CatProjectInfo} object immutable by causing the setter methods to throw
+    * exceptions instead of modifying values.
+    */
+
+   @JsonIgnore
+   private boolean locked;
+
+   /**
+    * Save the path to the PLE Configuration file for the CAT annotation processor.
+    */
+
+   private Path pleConfigurationPath;
+
+   /**
+    * Saves the name of the project the contained preferences are for.
+    */
+
+   private String projectName;
+
+   /**
+    * Saves the Source Location Method to be used by the CAT annotation processor.
+    */
+
+   private String sourceLocationMethod;
+
+   /**
+    * Creates a new mutable (open) {@link CatProjectInfo} object with <code>null</code> values.
+    */
+
+   public CatProjectInfo() {
+      this.projectName = null;
+      this.catJarPath = null;
+      this.sourceLocationMethod = null;
+      this.pleConfigurationPath = null;
+      this.locked = false;
+   }
+
+   /**
+    * Creates a new immutable (closed) {@link CatProjectInfo} object with the CAT Plug-In preference values from the
+    * <code>catParameters</code>.
+    * 
+    * @param project the {@link IProject} the {@link CatProjectInfo} object is being created for.
+    * @param catParameters a snapshot of the CAT Plug-In preference values.
+    */
+
+   public CatProjectInfo(IProject project, CatParameters catParameters) {
+      this.projectName = project.toString();
+      this.catJarPath = catParameters.getCatJarPath();
+      this.sourceLocationMethod = catParameters.getSourceLocationMethod();
+      this.pleConfigurationPath = catParameters.getPleConfigurationPath();
+      this.locked = true;
+   }
+
+   /**
+    * Throws a the {@link CatProjectInfo#lockedException} when this {@link CatProjectInfo} object has been locked
+    * (closed).
+    */
+
+   private void checkLock() {
+      if (this.locked) {
+         throw CatProjectInfo.lockedException;
+      }
+   }
+
+   /**
+    * Compares this {@link CatProjectInfo} object with another for equality.
+    * 
+    * @param otherCatProjectInfo the {@link CatProjectInfo} to be compared with.
+    * @return <code>true</code> when all members of both objects are equal; otherwise, <code>false</code>.
+    */
+
+   @JsonIgnore
+   public boolean equals(CatProjectInfo otherCatProjectInfo) {
+
+      //@formatter:off
+      return
+         Objects.nonNull( otherCatProjectInfo )
+      && CatProjectInfo.equals( this.projectName,          otherCatProjectInfo.projectName          )
+      && CatProjectInfo.equals( this.catJarPath,           otherCatProjectInfo.catJarPath           )
+      && CatProjectInfo.equals( this.sourceLocationMethod, otherCatProjectInfo.sourceLocationMethod )
+      && CatProjectInfo.equals( this.pleConfigurationPath, otherCatProjectInfo.pleConfigurationPath )
+      ; 
+      //@formatter:on
+   }
+
+   public Path getCatJarPath() {
+      return this.catJarPath;
+   }
+
+   public Path getPleConfigurationPath() {
+      return this.pleConfigurationPath;
+   }
+
+   public String getProjectName() {
+      return this.projectName;
+   }
+
+   public String getSourceLocationMethod() {
+      return this.sourceLocationMethod;
+   }
+
+   /**
+    * Closes this {@link CatProjectInfo} object making it immutable. The setter methods will throw the
+    * {@link CatProjectInfo#lockedException} once the {@link CatProjectInfo} object has been closed.
+    */
+
+   @JsonIgnore
+   public void close() {
+      this.locked = true;
+   }
+
+   public void setCatJarPath(Path catJarPath) {
+      this.checkLock();
+      this.catJarPath = catJarPath;
+   }
+
+   public void setPleConfigurationPath(Path pleConfigurationPath) {
+      this.checkLock();
+      this.pleConfigurationPath = pleConfigurationPath;
+   }
+
+   public void setProjectName(String projectName) {
+      this.checkLock();
+      this.projectName = projectName;
+   }
+
+   public void setSourceLocationMethod(String sourceLocationMethod) {
+      this.checkLock();
+      this.sourceLocationMethod = sourceLocationMethod;
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatProjectManager.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatProjectManager.java
new file mode 100644
index 0000000..8fc5809
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatProjectManager.java
@@ -0,0 +1,604 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.project;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ISaveContext;
+import org.eclipse.core.resources.ISaveParticipant;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.ICoreRunnable;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.osee.framework.jdk.core.type.Pair;
+import org.eclipse.ote.cat.plugin.CatPlugin;
+import org.eclipse.ote.cat.plugin.Constants;
+import org.eclipse.ote.cat.plugin.exception.CatErrorCode;
+import org.eclipse.ote.cat.plugin.exception.CatPluginException;
+import org.eclipse.ote.cat.plugin.preferencepage.Preference;
+import org.eclipse.ote.cat.plugin.util.Projects;
+import org.eclipse.ui.statushandlers.StatusManager;
+
+/**
+ * Manages a persistent cache of {@link CatProject}s. A CAT project is an Eclipse workspace project that has the
+ * {@link CatNature} applied.
+ * <p>
+ * <h2>Project Files</h2>
+ * <dl style="padding-left:3em">
+ * <dt>&quot;.project&quot; file:</dt>
+ * <dd>This file in the root directory of a project maintains the list of project natures that have been applied to the
+ * project.</dd>
+ * <dt>{@value org.eclipse.ote.cat.plugin.Constants#catProjectInfoFileName} file:</dt>
+ * <dd>This file in the root directory of a project maintains the CAT project settings. The settings file JSON contents
+ * are marshaled into and out of instances of the class {@link CatProjectInfo}.</dd>
+ * </dl>
+ * <p>
+ * <h2>Plug-In State File</h2>
+ * <p>
+ * The method {@link org.eclipse.core.runtime.Plugin#getStateLocation Plugin::getStateLocation} provides the workspace
+ * path to the state folder for the plug-in. The {@link CatProjectInfo} for each {@link CatProject} in the cache is
+ * saved to the file {@value org.eclipse.ote.cat.plugin.Constants#catPluginStateFile} as an JSON array of
+ * {@link CatProjectInfo} objects.
+ * </p>
+ * <h2>Preferences</h2>
+ * <p>
+ * The CAT Plug-In preference {@link Preference#JTS_PROJECTS} contains a list of the projects that are expected to have
+ * the {@link CatNature} applied.
+ * </p>
+ * <h2>Cache Initialization</h2>
+ * <p>
+ * The following steps are taken on CAT Plug-In startup to resolve potential differences in the three configuration
+ * sources.
+ * <ul>
+ * <li>The cache contents are loaded from the CAT Plug-In state file. A background job is started to complete the
+ * following:
+ * <ul>
+ * <li>Workspace projects that do not have a {@link CatNature} applied are removed from the cache.</li>
+ * <li>Workspace projects that have a {@link CatNature} that are not in the cache are added to the cache.</li>
+ * <li>Workspace projects that have a {@link CatNature} that are in the cache are updated with the
+ * {@link CatProjectInfo} data from the {@value org.eclipse.ote.cat.plugin.Constants#catProjectInfoFileName} file for
+ * the project.</li>
+ * <li>Projects on the {@link Preference#JTS_PROJECTS} list that are not in the cache have the {@link CatNature} applied
+ * using the current preference settings. The newly configured project is added to the cache.</li>
+ * <li>The {@link Preference#JTS_PROJECTS} is updated with a list of the projects in the cache.</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * </p>
+ * <h2>Preference Update</h2>
+ * <p>
+ * When the CAT Plug-In preferences are changed the Workspace projects are updated as follows:
+ * <ul>
+ * <li>Cached projects that are not on the {@link Preference#JTS_PROJECTS} list have the {@link CatNature} removed and
+ * are deconfigured for the CAT annotation processor.</li>
+ * <li>Cached projects that are on the {@link Preference#JTS_PROJECTS} list are updated with any preference
+ * changes.</li>
+ * <li>Projects on the {@link Preference#JTS_PROJECTS} list that are not in the cache have the {@link CatNature} applied
+ * and are configured for the CAT annotation processor with the current preferences.</li>
+ * </ul>
+ * </p>
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class CatProjectManager implements ISaveParticipant {
+
+   /**
+    * Internal class used to encapsulate the project cache and provide synchronized access.
+    */
+
+   private class CatProjectCache {
+
+      /**
+       * Cache of {@link CatProject}s by project name. This cache is initially populated from the CAT Plug-In state file
+       * before the {@link CatProject}s are reassociated with the Eclipse {@link IProject}s.
+       */
+
+      private Map<String, CatProject> byName;
+
+      /**
+       * Cache of {@link CatProject}s by the {@link IProject} they wrap.
+       */
+
+      private Map<IProject, CatProject> byProject;
+
+      /**
+       * Writing the CAT Plug-In state file is synchronized on this object instead of the {@link CatProjectCache}
+       * instance so the cache is not locked during a save operation.
+       */
+
+      private Object saveSyncObject;
+
+      /**
+       * Creates a new empty cache.
+       */
+
+      CatProjectCache() {
+         this.byName = new HashMap<>();
+         this.byProject = new HashMap<>();
+         this.saveSyncObject = new Object();
+      }
+
+      /**
+       * Adds a {@link CatProject} to the cache. When the {@link CatProject} does not contain a {@link IProject}
+       * reference the {@link CatProject} is only added to the {@link #byName} cache.
+       * 
+       * @param catProject the {@link CatProject} to be added.
+       */
+
+      synchronized public void add(CatProject catProject) {
+         IProject project = catProject.getProject();
+         if (Objects.nonNull(project)) {
+            this.byName.put(project.toString(), catProject);
+            this.byProject.put(project, catProject);
+         } else {
+            CatProjectInfo catProjectInfo = catProject.getCatProjectInfo();
+            String name = catProjectInfo.getProjectName();
+            this.byName.put(name, catProject);
+         }
+      }
+
+      /**
+       * Gets the {@link CatProject} for the <code>project</code>.
+       * 
+       * @param project the {@link IProject} to get the associated {@link CatProject} for.
+       * @return an {@link Optional} containing the {@link CatProject} associated with the <code>project</code>;
+       * otherwise, an empty {@link Optional}.
+       */
+
+      synchronized public Optional<CatProject> get(IProject project) {
+         CatProject catProject = this.byProject.get(project);
+         if (Objects.isNull(catProject)) {
+            catProject = this.byName.get(project.toString());
+            if (Objects.nonNull(catProject)) {
+               this.byProject.put(project, catProject);
+            }
+         }
+         return Optional.ofNullable(catProject);
+      }
+
+      /**
+       * A list of the names of the projects in the cache. The returned list is not backed by the cache.
+       * 
+       * @return a {@link List} of the names of the {@link CatProject}s in the cache.
+       */
+
+      synchronized List<String> getNames() {
+         List<String> list = new LinkedList<>();
+         this.byName.keySet().forEach(list::add);
+         return list;
+      }
+
+      /**
+       * Removes the {@link CatProject} associated with the <code>project</code> from the cache.
+       * 
+       * @param project the {@link IProject} to remove it's associated {@link CatProject}.
+       * @return an {@link Optional} containing the removed {@link CatProject} when the cache contained an association;
+       * otherwise, an empty {@link Optional}.
+       */
+
+      synchronized public Optional<CatProject> remove(IProject project) {
+         String name = project.toString();
+         CatProject catProjectByName = this.byName.remove(name);
+         CatProject catProjectByProject = this.byProject.remove(project);
+         if (Objects.nonNull(catProjectByName)) {
+            return Optional.of(catProjectByName);
+         }
+         return Optional.ofNullable(catProjectByProject);
+      }
+
+      /**
+       * Extracts the {@link CatProjectInfo} objects from the {@link CatProject}s in the cache and saves then in the CAT
+       * Plug-In state file. The cache is locked while the {@link CatProjectInfo} objects are being extracted to an
+       * array. Once the extraction is complete the cache lock is released. The file write is synchronized on the
+       * {@link #saveSyncObject} to prevent more than one thread from attempting to write the file.
+       */
+
+      void save() {
+
+         CatProjectInfo[] catProjectInfoArray;
+
+         synchronized (this) {
+         //@formatter:off
+         catProjectInfoArray =
+            this.byName
+               .values()
+               .stream()
+               .map( CatProject::getCatProjectInfo )
+               .collect( Collectors.toCollection( ArrayList::new ) )
+               .toArray( new CatProjectInfo[this.byName.size()] )
+               ;
+         //@formatter:on
+         }
+
+         synchronized (saveSyncObject) {
+            File stateLocationFile = CatPlugin.getStateLocationFile();
+            CatProjectsInfo catProjectsInfo = new CatProjectsInfo(catProjectInfoArray);
+            CatProjectsInfo.write(stateLocationFile, catProjectsInfo);
+         }
+      }
+
+   }
+
+   /**
+    * When the <code>optional</code> contains a value the <code>presentAction</code> will be performed with
+    * <code>optional</code> value; otherwise, the <code>elseAction</code> is performed.
+    * 
+    * @param optional the {@link Optional} to process.
+    * @param presentAction {@link Consumer} performed with the <code>optional</code> contents when present.
+    * @param elseAction {@link Runnable} performed when the <code>optional</code> is empty.
+    * @implNote With Java 9+ this method will no longer be necessary as the method Optional::ifPresentOrElse is
+    * available.
+    */
+
+   private static void ifPresentOrElse(Optional<CatProject> optional, Consumer<CatProject> presentAction, Runnable elseAction) {
+      //TODO: remove this method and refactor with Java 9+
+      if (optional.isPresent()) {
+         presentAction.accept(optional.get());
+      } else {
+         elseAction.run();
+      }
+   }
+
+   /**
+    * Saves the cache of {@link CatProject}s by name and by {@link IProject}.
+    */
+
+   protected CatProjectCache catProjectCache;
+
+   /**
+    * Creates an unititialized {@link CatProjectManager} instance.
+    * 
+    * @implNote The {@link CatPlugin} constructor creates the {@link CatProjectManager}. The
+    * {@link CatProjectManager#start} method is called by the {@link CatPlugin#start} bundle activator to initialize the
+    * {@link CatProjectManager}.
+    */
+
+   public CatProjectManager() {
+      this.catProjectCache = null;
+   }
+
+   /**
+    * Adds the <code>catProject</code> to the cache replacing any {@link CatProject} instances that were for the same
+    * {@link IProject}.
+    * 
+    * @param catProject the {@link CatProject} instance to cache.
+    */
+
+   public void addCatProject(CatProject catProject) {
+      this.catProjectCache.add(catProject);
+   }
+
+   /**
+    * No action by the {@link CatProjectManager} is performed when the workspace save is completed.
+    * <p>
+    * {@inheritDoc}
+    */
+
+   @Override
+   public void doneSaving(ISaveContext context) {
+      // nothing to do
+   }
+
+   public Optional<CatProject> getCatProject(IProject project) {
+      return this.catProjectCache.get(project);
+   }
+
+   /**
+    * No action by the {@link CatProjectManager} is performed in preparation for a workspace save.
+    * <p>
+    * {@inheritDoc}
+    */
+
+   @Override
+   public void prepareToSave(ISaveContext context) throws CoreException {
+      // nothing to do
+   }
+
+   /**
+    * Removes the {@link CatProject} associated with the {@link IProject} from the cache.
+    * 
+    * @param project the {@link IProject} whose associated {@link CatProject} is to be removed from the cache.
+    * @return an {@link Optional} containing the removed {@link CatProject} when the cache contained an association;
+    * otherwise, an empty {@link Optional}.
+    */
+   public Optional<CatProject> removeCatProject(IProject project) {
+      return this.catProjectCache.remove(project);
+   }
+
+   /**
+    * The {@link CatProjectManager} does not support rollback operations.
+    * <p>
+    * {@inheritDoc}
+    */
+
+   @Override
+   public void rollback(ISaveContext context) {
+      // nothing to do
+   }
+
+   /**
+    * Saves the cache to the CAT Plug-In state file for a workspace save operation.
+    * <p>
+    * {@inheritDoc}
+    */
+
+   @Override
+   public void saving(ISaveContext context) throws CoreException {
+      this.catProjectCache.save();
+   }
+
+   /**
+    * Initializes the {@link CatProjectManager} as follows:
+    * <ul>
+    * <li>Creates the {@link CatProjectCache}.</li>
+    * <li>Adds the {@link CatProjectManager} to the workspace save participant list.</li>
+    * <li>Reads the CAT Plug-In state file and initializes the {@link CatProjectCache} from it's contents.</li>
+    * <li>Creates and starts a background job to synchronize the {@link CatProjectCache} with the current state of the
+    * projects in the workspace.</li>
+    * </ul>
+    * 
+    * @throws CatPluginException when unable to initialize the {@link CatProjectManager}.
+    */
+
+   public void start() {
+      try {
+         this.catProjectCache = new CatProjectCache();
+         IWorkspace workspace = ResourcesPlugin.getWorkspace();
+         workspace.addSaveParticipant(CatPlugin.getIdentifier(), this);
+         File stateLocationFile = CatPlugin.getStateLocationFile();
+         if (stateLocationFile.canRead()) {
+            CatProjectsInfo.read(stateLocationFile, this.catProjectCache::add);
+         }
+         this.updateProjectNatures();
+      } catch (Exception e) {
+         //@formatter:off
+         throw new 
+            CatPluginException
+               (
+                  CatErrorCode.InternalError,
+                  "Failed to start the CAT Project Manager." + "\n",
+                  e
+               );
+         //@formatter:off
+      }
+   }
+
+   /**
+    * Removes the {@link CatProjectManager} from the workspace's list of save participants.
+    */
+   
+   public void stop() {
+      this.catProjectCache = null;
+      IWorkspace workspace = ResourcesPlugin.getWorkspace();
+      workspace.removeSaveParticipant(CatPlugin.getIdentifier());
+   }
+
+   /**
+    * When a {@link CatProject} is associated with the <code>project</code> it is updated from the project's {@value Constants#catProjectInfoFileName} file
+    * and updated in the cache if changed; otherwise, a new {@link CatProject} is created for the <code>project</code> and cached.
+    * @param project the workspace project with a {@link CatNature} to be updated or cached.
+    * @implNote This method is only called with <code>project</code>'s that are known to have a {@link CatNature}.
+    */
+
+   private void synchronizeProject(IProject project) {
+      //TODO: refactor with Java 9+
+      //@formatter:off
+      ifPresentOrElse
+         (
+            this.catProjectCache.get(project),
+            ( catProject ) -> CatProject.updateCatProject(catProject,project).ifPresent(this.catProjectCache::add),
+            () -> this.catProjectCache.add( CatProject.create( project ) )
+         );
+      //@formatter:on
+   }
+
+   /**
+    * This method performs the synchronization of the cache and preferences.
+    * 
+    * @param projectsWithNature a {@link Map} of all projects in the workspace with the {@link CatNature}.
+    * @param projectsWithoutNature an {@link Map} of all projects in the workspace without the {@link CatNature}.
+    * @see {@link #updateProjectNatures()}.
+    * @implNote All exceptions are caught and then logged with the {@link StatusManager}.
+    */
+
+   private void synchronizeProjectsWithPreferences(Map<String, IProject> projectsWithNature, Map<String, IProject> projectsWithoutNature) {
+
+      /*
+       * Get a comma separated list of the names of the projects that should have the CAT Nature from the preferences.
+       * Projects not on this list with the CAT Nature will have the CAT Nature removed.
+       */
+
+      String projectPreferenceString = Preference.JTS_PROJECTS.get();
+
+      if (Objects.nonNull(projectPreferenceString) && !projectPreferenceString.isEmpty()) {
+
+         String[] projectNames = projectPreferenceString.split(",");
+
+         /*
+          * Loop the names of projects that should be configured with the CAT Nature.
+          */
+
+         for (String projectName : projectNames) {
+
+            IProject project;
+
+            if (Objects.nonNull(project = projectsWithNature.get(projectName))) {
+
+               /*
+                * Project has CatNature
+                */
+
+               /*
+                * Move CatNature to first position, if it is not first
+                */
+
+               int position = Projects.position(project, CatPlugin.getIdentifier());
+               if (position > 0) {
+                  Projects.moveNatureToTheFirstPosition(project, position);
+               }
+
+               /*
+                * Update the compiler options for the project
+                */
+
+               this.getCatProject(project).ifPresent(CatProject::update);
+
+               /*
+                * Remove updated project from the set of projects with the CAT Nature. The remaining projects after this
+                * loop need to have the CAT Nature removed.
+                */
+
+               projectsWithNature.remove(projectName);
+
+            } else if (Objects.nonNull(project = projectsWithoutNature.get(projectName))) {
+
+               /*
+                * Add the CatNature to the project. This will invoke the configure method to set the compiler options
+                * and add the project to the cache.
+                */
+
+               Projects.addNature(project, CatPlugin.getCatNatureIdentifier());
+
+            }
+         }
+      }
+
+      /*
+       * Leftover projects in projectsWithNature need to have the CAT Nature removed.
+       */
+
+      for (IProject project : projectsWithNature.values()) {
+
+         /*
+          * Remove the CatNature from the project. This will invoke the deconfigure method to remove the compiler
+          * options from the project and remove the project from the cache.
+          */
+
+         Projects.removeProjectNature(project, CatPlugin.getCatNatureIdentifier());
+      }
+
+      List<String> names = this.catProjectCache.getNames();
+
+      names.sort((a, b) -> a.compareTo(b));
+      String nameCommaList = names.stream().collect(Collectors.joining(","));
+      Preference.JTS_PROJECTS.set(nameCommaList);
+      CatPlugin.savePreferences();
+   }
+
+   /**
+    * This method performs the synchronization of the cache, projects, and preferences.
+    * 
+    * @see {@link #updateProjectNatures()}.
+    * @implNote All exceptions are caught and then logged with the {@link StatusManager}.
+    */
+
+   synchronized private void synchronizeProjects() {
+
+      try {
+
+         /*
+          * Get all workspace projects separated by those with the CAT Nature and those without.
+          */
+
+         //@formatter:off
+         Pair<HashMap<String, IProject>, HashMap<String, IProject>> pair = 
+            Projects.getProjectsForNature
+               (
+                  CatPlugin.getCatNatureIdentifier(), 
+                  HashMap::new,
+                  (map, project) -> map.put(project.toString(),project)
+               );
+         HashMap<String, IProject> projectsWithNature = pair.getFirst();
+         HashMap<String, IProject> projectsWithoutNature = pair.getSecond();
+         //@formatter:on
+
+         /*
+          * Ensure cached data matches project data for projects with the CatNature. Project data takes precedence over
+          * cached data.
+          */
+
+         projectsWithNature.values().forEach(this::synchronizeProject);
+
+         /*
+          * Remove all projects that do not have the CatNature from the cache.
+          */
+
+         projectsWithoutNature.values().forEach(this.catProjectCache::remove);
+
+         /*
+          * Ensure projects specified in the preferences have the CatNature and remove the CatNature from projects not
+          * specified in the preferences.
+          */
+
+         this.synchronizeProjectsWithPreferences(projectsWithNature, projectsWithoutNature);
+
+         /*
+          * Persist preferences and cache
+          */
+
+         CatPlugin.savePreferences();
+         this.catProjectCache.save();
+
+      } catch (CatPluginException cpe) {
+         cpe.log();
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException synchronizeProjectsException =
+            new CatPluginException
+                   (
+                      CatErrorCode.InternalError,
+                      "Failed to synchronize CAT Project Manager with preferences." + "\n",
+                      e
+                   );
+         //@formatter:on
+         synchronizeProjectsException.log();
+      }
+   }
+
+   /**
+    * Starts a background job to synchronize the cache, project, and preferences as follows:
+    * <ul>
+    * <li>The {@value Constants#catProjectInfoFileName} file for all projects with the {@link CatNature} is read and the
+    * cache is updated with any changes to the {@link CatProjectInfo} that were found.</li>
+    * <li>Projects without the {@link CatNature} that are also in the cache are removed from the cache.</li>
+    * <li>Any project listed in the preferences that is not in the cache will have the {@link CatNature} added to the
+    * project and the project is added to the cache.</li>
+    * <li>Any project in the cache that is not also listed in the preferences will have the {@link CatNature} removed
+    * and also be removed from the cache.</li>
+    * </ul>
+    * At the completion of the job, the preferences and cache are persisted.
+    */
+
+   public void updateProjectNatures() {
+      //@formatter:off
+      Job job = 
+         Job.create
+            (
+               "Update CAT Project Manager Cache",
+               (ICoreRunnable) monitor -> this.synchronizeProjects()
+            );
+      //@formatter:on
+      job.schedule();
+   }
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatProjectsInfo.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatProjectsInfo.java
new file mode 100644
index 0000000..1ef1ae0
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/project/CatProjectsInfo.java
@@ -0,0 +1,132 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.project;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.function.Consumer;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.ote.cat.plugin.Constants;
+import org.eclipse.ote.cat.plugin.exception.CatErrorCode;
+import org.eclipse.ote.cat.plugin.exception.CatPluginException;
+import org.eclipse.ote.cat.plugin.util.JsonFileOperations;
+
+/**
+ * Encapsulates an array of CAT annotation processor parameters for the projects in the workspace with the CAT nature.
+ * This class is marshaled to and from the {@value Constants#catPluginStateFile} plug-in state file.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class CatProjectsInfo {
+
+   /**
+    * Saves a single instance of a {@link JsonFileOperations} object specialized for reading and writing
+    * {@link CatProjectsInfo} objects to and from JSON files.
+    */
+
+   //@formatter:off
+   private static final JsonFileOperations<CatProjectsInfo> jsonFileOperations =
+      new JsonFileOperations<>
+             (
+                CatProjectsInfo.class,
+                Constants.catPluginStateFileDescription
+             );
+   //@formatter:on
+
+   /**
+    * Reads the <code>file</code> and parses the contents as a JSON array of {@link CatInfoProject} objects. A
+    * {@link CatProject} without an {@link IProject} reference is created for each {@link CatInfoProject} read. The
+    * {@link CatProject} objects are provided to the <code>adder</code> as they are created.
+    * 
+    * @param file the file to read from.
+    * @param adder a {@link Consumer}
+    */
+
+   public static void read(File file, Consumer<CatProject> adder) {
+
+      CatProjectsInfo catProjectsInfo = null;
+
+      try {
+         catProjectsInfo = (CatProjectsInfo) CatProjectsInfo.jsonFileOperations.read(file);
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException catPluginStateFileWriteException =
+            new CatPluginException
+                   (
+                      CatErrorCode.CatPluginStateFileError,
+                        "Failed to read the CAT Plugin state file." + "\n"
+                      + "File: " + file.getPath().toString()        + "\n"
+                   );
+         //@formatter:off
+         throw catPluginStateFileWriteException;
+      }
+      
+      //@formatter:off
+      Arrays
+         .stream( catProjectsInfo.getCatProjectInfo() )
+         .filter( ( catProjectInfo ) -> Objects.nonNull( catProjectInfo.getProjectName() ) )
+         .map( CatProject::new )
+         .forEach( adder );
+      //@formatter:on
+   }
+
+   /**
+    * Writes the <code>catProjectsInfo</code> to the <code>file</code> as a JSON array of {@link CatProjectInfo}
+    * objects.
+    * 
+    * @param file the file to be written to.
+    * @param catProjectsInfo the {@link CatProjectInfo} objects to be saved.
+    */
+
+   public static void write(File file, CatProjectsInfo catProjectsInfo) {
+      try {
+         CatProjectsInfo.jsonFileOperations.write(file, catProjectsInfo);
+      } catch (Exception e) {
+         //@formatter:off
+         CatPluginException catPluginStateFileWriteException =
+            new CatPluginException
+                   (
+                      CatErrorCode.CatPluginStateFileError,
+                        "Failed to write the CAT Plugin state file." + "\n"
+                      + "File: " + file.getPath().toString()         + "\n"
+                   );
+         //@formatter:off
+         throw catPluginStateFileWriteException;
+      }
+   }
+
+   /**
+    * Saves an unordered array of {@link CatProjectInfo} objects.
+    */
+   
+   private CatProjectInfo[] catProjectInfo;
+
+   public CatProjectsInfo() {
+      this.catProjectInfo = null;
+   }
+
+   public CatProjectsInfo(CatProjectInfo[] catProjectInfo) {
+      this.catProjectInfo = catProjectInfo;
+   }
+
+   public CatProjectInfo[] getCatProjectInfo() {
+      return this.catProjectInfo;
+   }
+
+   public void setCatProjectInfo(CatProjectInfo[] catProjectInfo) {
+      this.catProjectInfo = catProjectInfo;
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/util/Extensions.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/util/Extensions.java
new file mode 100644
index 0000000..1336f26
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/util/Extensions.java
@@ -0,0 +1,258 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.util;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtension;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.osee.framework.jdk.core.type.OseeCoreException;
+
+/**
+ * A class of static methods for accessing the Extension Registry.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class Extensions {
+
+   /**
+    * Gets the attribute value for the attribute specified by <code>attributeName</code> from the
+    * <code>configurationElement</code>.
+    * 
+    * @param configurationElement the configuration element to obtain the specified attribute value from.
+    * @param attributeName the name of the attribute to get a value for.
+    * @return the named attribute's value from the <code>configurationElement</code>.
+    * @throws OseeCoreException when:
+    * <ul>
+    * <li>The <code>configurationElement</code> does not have an attribute named <code>attributeName</code>.</li>
+    * <li>An error occurred accessing the <code>configurationElement</code>'s attributes.</li>
+    * </ul>
+    */
+
+   public static String getAttribute(IConfigurationElement configurationElement, String attributeName) {
+      String option = null;
+      Exception optionCause = null;
+      try {
+         option = configurationElement.getAttribute(attributeName);
+      } catch (Exception e) {
+         optionCause = e;
+      }
+      if (Objects.isNull(option)) {
+         //@formatter:off
+         OseeCoreException cannotDetermineCommandLineOptionNameForDefaults =
+            new OseeCoreException
+                   (
+                        "Failed to get Attribute value from then Configuration Element." + "\n"
+                      + "   Configuration Element: " + configurationElement.getName()    + "\n"
+                      + "   Attribute Name:        " + attributeName                     + "\n",
+                      optionCause // <- might be null
+                   );
+         //@formatter:on
+         throw cannotDetermineCommandLineOptionNameForDefaults;
+      }
+      return option;
+   }
+
+   /**
+    * Gets all Configuration Elements from the Extension with the name specified by
+    * <code>configurationElementName</code>.
+    * 
+    * @param extension the {@link IExtension} to search for {@link IConfigurationElement}s.
+    * @param configurationElementName the name of the Configuration Elements to get.
+    * @param expectedCount when greater than or equal to 0, the expected number of Configuration Elements.
+    * @return a {@link List} containing the {@link IConfigurationElement}'s of the {@link IExtension} with the name
+    * <code>configurationElementName</code>.
+    * @throws OseeCoreException when:
+    * <ul>
+    * <li>When a error occurs obtaining Configuration Elements from the Extension.</li>
+    * <li>When <code>expectedCount</code> is greater than or equal to 0 and the number of Configuration Elements found
+    * with the name <code>configurationElementName</code> does not equal the <code>expectedCount</code>.</li>
+    * </ul>
+    */
+
+   public static List<IConfigurationElement> getConfigurationElements(IExtension extension, String configurationElementName, int expectedCount) {
+
+      IConfigurationElement[] configurationElements = null;
+      Exception configurationElementsCause = null;
+
+      try {
+         configurationElements = extension.getConfigurationElements();
+      } catch (Exception e) {
+         configurationElementsCause = e;
+      }
+
+      if (Objects.isNull(configurationElements)) {
+         //@formatter:off
+         OseeCoreException osgiConfigurationElementsNotFound =
+            new OseeCoreException
+                   (
+                        "Configuration Elements of the Extension were not found."                         + "\n"
+                      + "   Extension:                  " + extension.getExtensionPointUniqueIdentifier() + "\n"
+                      + "   Configuration Element Name: " + configurationElementName                      + "\n"
+                      + "   Expected Count:             " + expectedCount                                 + "\n",
+                      configurationElementsCause
+                   );
+         //@formatter:on
+         throw osgiConfigurationElementsNotFound;
+      }
+
+      List<IConfigurationElement> configurationElementsList = new LinkedList<>();
+      Exception configurationElementCause = null;
+
+      try {
+         for (int i = 0; i < configurationElements.length; i++) {
+            if (Objects.nonNull(configurationElements[i]) && configurationElementName.equals(
+               configurationElements[i].getName())) {
+               configurationElementsList.add(configurationElements[i]);
+               break;
+            }
+         }
+      } catch (Exception e) {
+         configurationElementCause = e;
+      }
+
+      if ((expectedCount >= 0) && (configurationElementsList.size() != expectedCount)) {
+         //@formatter:off
+         OseeCoreException cannotFindInitializerConfigurationElement =
+            new OseeCoreException
+                   (
+                        "An unexpected number of Configuration Elements were found for the extension."    + "\n"
+                      + "   Actual Count:               " + configurationElements.length                  + "\n"
+                      + "   Extension:                  " + extension.getExtensionPointUniqueIdentifier() + "\n"
+                      + "   Configuration Element Name: " + configurationElementName                      + "\n"
+                      + "   Expected Count:             " + expectedCount                                 + "\n",
+                      configurationElementCause // <- might be null
+                   );
+         //@formatter:on
+         throw cannotFindInitializerConfigurationElement;
+      }
+
+      return configurationElementsList;
+   }
+
+   /**
+    * Finds all the extensions provided by a bundle for an extension point.
+    * 
+    * @param extensionPointIdentifier the identifier of the extension point to get the extensions for.
+    * @param bundleSymbolicName the identifier of the bundle to get the provided extensions for.
+    * @param expectedCount when greater than or equal to 0, the expected number of extensions to be found.
+    * @return a {@link List} of the found extensions. An empty list is returned when no extensions are found.
+    * @throws OseeCoreException when:
+    * <ul>
+    * <li>The extension registry is not available or an error occurs when obtaining it.</li>
+    * <li>The extension point cannot be found or an error occurs searching for the extension point.</li>
+    * <li>An error occurs obtaining the extensions for the extension point.</li>
+    * <li><code>expectedCount</code> is greater than or equal to 0, and <code>expectedCount</code> s from the
+    * <code>bundleSymbolicName</code> bundle were not found.</li>
+    * </ul>
+    */
+
+   public static List<IExtension> getExtensions(String extensionPointIdentifier, String bundleSymbolicName, int expectedCount) {
+
+      IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
+
+      if (Objects.isNull(extensionRegistry)) {
+         //@formatter:off
+         OseeCoreException osgiExtensionRegistryNotAvailable =
+            new OseeCoreException
+                   (
+                        "The Extension Registry is not available. Unable to find extensions for the extenion point." + "\n"
+                      + "   Extension Point:      " + extensionPointIdentifier                                       + "\n"
+                      + "   Bundle Symbolic Name: " + bundleSymbolicName                                             + "\n"
+                      + "   Expected Count:       " + expectedCount                                                  + "\n"
+                   );
+         //@formatter:on
+         throw osgiExtensionRegistryNotAvailable;
+      }
+
+      IExtensionPoint extensionPoint = extensionRegistry.getExtensionPoint(extensionPointIdentifier);
+
+      if (Objects.isNull(extensionPoint)) {
+         //@formatter:off
+         OseeCoreException osgiExtensionPointNotFound =
+            new OseeCoreException
+                   (
+                        "The Extension Point was not found."                    + "\n" 
+                      + "   Extension Point:      " + extensionPointIdentifier  + "\n"
+                      + "   Bundle Symbolic Name: " + bundleSymbolicName        + "\n"
+                      + "   Expected Count:       " + expectedCount             + "\n"
+                   );
+         //@formatter:on
+         throw osgiExtensionPointNotFound;
+      }
+
+      IExtension[] extensions = null;
+      Exception extensionCause = null;
+
+      extensionPoint.getExtensions();
+
+      try {
+         extensions = extensionPoint.getExtensions();
+      } catch (Exception e) {
+         extensionCause = e;
+      }
+
+      if (Objects.isNull(extensions)) {
+         //@formatter:off
+         OseeCoreException osgiExtensionNotFound =
+            new OseeCoreException
+                   (
+                        "An Extension was not found for the Extension Point."   + "\n" 
+                      + "   Extension Point:      " + extensionPointIdentifier  + "\n"
+                      + "   Bundle Symbolic Name: " + bundleSymbolicName        + "\n"
+                      + "   Expected Count:       " + expectedCount             + "\n",
+                      extensionCause // <- might be null
+                   );
+         //@formatter:on
+         throw osgiExtensionNotFound;
+      }
+
+      List<IExtension> extensionsWithNamespace = new LinkedList<>();
+
+      for (int i = 0; i < extensions.length; i++) {
+         if (extensions[i].getNamespaceIdentifier().equals(bundleSymbolicName)) {
+            extensionsWithNamespace.add(extensions[i]);
+         }
+      }
+
+      if ((expectedCount >= 0) && (extensionsWithNamespace.size() != expectedCount)) {
+         //@formatter:off
+         OseeCoreException unexpectedNumberOfExtensionsException =
+            new OseeCoreException
+                   (
+                        "An unexpected number of extensions are provided by the bundle." + "\n"
+                      + "   Actual Count:         " + extensionsWithNamespace.size()     + "\n"
+                      + "   Extension Point:      " + extensionPointIdentifier           + "\n"
+                      + "   Bundle Symbolic Name: " + bundleSymbolicName                 + "\n"
+                      + "   Expected Count:       " + expectedCount                      + "\n"
+                   );
+         //@formatter:on
+         throw unexpectedNumberOfExtensionsException;
+      }
+
+      return extensionsWithNamespace;
+   }
+
+   /**
+    * Constructor is private to prevent instantiation of the class.
+    */
+
+   private Extensions() {
+   }
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/util/JsonFileOperations.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/util/JsonFileOperations.java
new file mode 100644
index 0000000..5c35beb
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/util/JsonFileOperations.java
@@ -0,0 +1,339 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Objects;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.osee.framework.jdk.core.type.OseeCoreException;
+
+/**
+ * An {@link ObjectMapper} wrapper with methods for reading and writing JSON files.
+ * 
+ * @author Loren K. Ashley
+ * @param <T> The type of object marshaled to and from JSON.
+ */
+
+public class JsonFileOperations<T> {
+
+   /**
+    * An extension of {@link ByteArrayOutputStream} that provides access to it's internal buffer.
+    */
+
+   private class BufferByteArrayOutputStream extends ByteArrayOutputStream {
+      //TODO: With Java 9+ an anonymous class can be used and this internal class can be removed.
+
+      /**
+       * Gets the streams byte buffer. Do not access the byte buffer until after writing to the stream is complete. The
+       * wrapped {@link ByteArrayOutputStream} may reallocate it's internal buffer during writing.
+       * 
+       * @return the byte buffer.
+       */
+
+      byte[] getBuffer() {
+         return this.buf;
+      }
+   }
+
+   /**
+    * Saves a description of the type of file being read or written for use in exception messages.
+    */
+
+   private final String fileDescription;
+
+   /**
+    * Saves the {@link ObjectMapper} used for marshaling objects of class &lt;T&gt;.
+    */
+
+   ObjectMapper objectMapper;
+
+   /**
+    * Saves the {@link Class} of the object being marshaled to and from file.
+    */
+
+   private final Class<T> pojoClass;
+
+   /**
+    * Creates a JSON file reader writer for objects of the class &lt;T&gt;.
+    * 
+    * @param pojoClass the class of the object to marshaled to and from JSON files.
+    * @param fileDescription a description of the type of file being read or written for use in exception messages.
+    */
+
+   public JsonFileOperations(Class<T> pojoClass, String fileDescription) {
+      this.pojoClass = pojoClass;
+      this.fileDescription = fileDescription;
+      this.objectMapper = new ObjectMapper();
+   }
+
+   /**
+    * Reads the JSON <code>file</code> ({@link File}) and returns the contents as an object of class &lt;T&gt;.
+    * 
+    * @param file the file to be read.
+    * @return an object of class &lt;T&gt; containing the file contents.
+    * @throws OseeCoreException when unable to access the <code>file</code> or unable to parse the JSON.
+    */
+
+   public T read(File file) {
+
+      Exception fileException = null;
+
+      try {
+
+         if (Objects.isNull(file) || !file.canRead()) {
+            //@formatter:off
+               fileException =
+                  new OseeCoreException
+                         (
+                              "The JSON file does not exsit or cannot be read."                                + "\n"
+                            + "   File Description: " + this.fileDescription                                   + "\n"
+                            + "   File:             " + ( Objects.isNull( file ) ? "(null)" : file.getPath() ) + "\n"
+                         );
+               //@formatter:on
+         }
+
+      } catch (Exception e) {
+         fileException = e;
+      }
+
+      if (Objects.nonNull(fileException)) {
+         if (fileException instanceof OseeCoreException) {
+            throw (OseeCoreException) fileException;
+         } else {
+            //@formatter:off
+               OseeCoreException systemFileException =
+                  new OseeCoreException
+                         (
+                              "An error occurred testing the accessability of the JSON file."                     + "\n"
+                               + "   File Description: " + this.fileDescription                                   + "\n"
+                               + "   File:             " + ( Objects.isNull( file ) ? "(null)" : file.getPath() ) + "\n",
+                               fileException
+                         );
+               //@formatter:on
+            throw systemFileException;
+         }
+      }
+
+      InputStream inputStream = null;
+
+      try {
+         inputStream = new FileInputStream(file);
+      } catch (Exception e) {
+         //@formatter:off
+         OseeCoreException getInputStreamException =
+            new OseeCoreException
+                   (
+                        "Failed to get InputStream for the JSON file."                                   + "\n"
+                      + "   File Description: " + this.fileDescription                                   + "\n"
+                      + "   File:             " + ( Objects.isNull( file ) ? "(null)" : file.getPath() ) + "\n",
+                      e
+                   );
+         //@formatter:on
+         throw getInputStreamException;
+      }
+
+      T pojo = this.read(inputStream);
+
+      return pojo;
+   }
+
+   /**
+    * Reads the JSON <code>iFile</code> ({@link IFile}) and returns the contents as an object of class &lt;T&gt;.
+    * 
+    * @param file the file to be read.
+    * @return an object of class &lt;T&gt; containing the file contents.
+    * @throws OseeCoreException when unable to access the <code>iFile</code> or unable to parse the JSON.
+    */
+
+   public T read(IFile iFile) {
+
+      Exception fileException = null;
+
+      try {
+
+         if (Objects.isNull(iFile) || !iFile.exists()) {
+            //@formatter:off
+               fileException =
+                  new OseeCoreException
+                         (
+                              "The JSON file does not exsit."                                                                     + "\n"
+                            + "   File Description: " + this.fileDescription                                                      + "\n"
+                            + "   IFile:            " + ( Objects.isNull( iFile ) ? "(null)" : iFile.getLocation().toOSString() ) + "\n"
+                         );
+               //@formatter:on
+         }
+
+      } catch (Exception e) {
+         fileException = e;
+      }
+
+      if (Objects.nonNull(fileException)) {
+         if (fileException instanceof OseeCoreException) {
+            throw (OseeCoreException) fileException;
+         } else {
+            //@formatter:off
+               OseeCoreException systemFileException =
+                  new OseeCoreException
+                         (
+                              "An error occurred testing the accessability of the JSON file."                                        + "\n"
+                               + "   File Description: " + this.fileDescription                                                      + "\n"
+                               + "   File:             " + ( Objects.isNull( iFile ) ? "(null)" : iFile.getLocation().toOSString() ) + "\n",
+                               fileException
+                         );
+               //@formatter:on
+            throw systemFileException;
+         }
+      }
+
+      InputStream inputStream = null;
+
+      try {
+         inputStream = iFile.getContents();
+      } catch (Exception e) {
+         //@formatter:off
+         OseeCoreException getInputStreamException =
+            new OseeCoreException
+                   (
+                        "Failed to get InputStream for the JSON file."                                                      + "\n"
+                      + "   File Description: " + this.fileDescription                                                      + "\n"
+                      + "   File:             " + ( Objects.isNull( iFile ) ? "(null)" : iFile.getLocation().toOSString() ) + "\n",
+                      e
+                   );
+         //@formatter:on
+         throw getInputStreamException;
+      }
+
+      T pojo = this.read(inputStream);
+
+      return pojo;
+   }
+
+   /**
+    * Reads contents of the <code>inputStream</code> as JSON and returns the contents as an object of class &lt;T&gt;.
+    * 
+    * @param inputStream the {@link InputStream} to read from.
+    * @return an object of class &lt;T&gt; containing the <code>inputStream</code> contents.
+    * @throws OseeCoreException when unable to read from the <code>inputStream</code> or unable to parse the JSON.
+    */
+
+   public T read(InputStream inputStream) {
+
+      if (Objects.isNull(inputStream)) {
+         //@formatter:off
+         OseeCoreException inputStreamException =
+            new OseeCoreException
+                   (
+                        "The JSON InputStream is null."                + "\n"
+                      + "   File Description: " + this.fileDescription + "\n"
+                   );
+         //@formatter:on
+         throw inputStreamException;
+      }
+
+      T pojo = null;
+      Exception pojoReadException = null;
+
+      try (InputStream autoCloser = inputStream) {
+         pojo = this.objectMapper.readValue(inputStream, this.pojoClass);
+      } catch (Exception e) {
+         pojoReadException = e;
+      }
+
+      if (Objects.isNull(pojo) || Objects.nonNull(pojoReadException)) {
+         //@formatter:off
+            OseeCoreException failedToReadPojoException =
+               new OseeCoreException
+                      (
+                           "Failed to parse the JSON file."                + "\n"
+                         + "   File Description: " + this.fileDescription  + "\n",
+                         pojoReadException // <- might be null
+                      );
+            //@formatter:on
+         throw failedToReadPojoException;
+      }
+
+      return pojo;
+   };
+
+   /**
+    * Writes the <code>pojo</code> of class &lt;T&gt; to the <code>file</code> ({@link File}).
+    * 
+    * @param file the file to write the <code>pojo</code> to in JSON.
+    * @param pojo the object of class &lt;T&gt; to be marshaled to JSON.
+    * @throws OseeCoreException when unable to write the <code>file</code> of JSON marshaling fails.
+    */
+
+   public void write(File file, T pojo) {
+
+      try {
+         this.objectMapper.writeValue(file, pojo);
+      } catch (Exception e) {
+         //@formatter:off
+            OseeCoreException writeException =
+               new OseeCoreException
+                      (
+                           "Failed to write JSON file." + "\n"
+                         + "   File Description: " + this.fileDescription                                   + "\n"
+                         + "   File:             " + ( Objects.isNull( file ) ? "(null)" : file.getPath() ) + "\n",
+                         e
+                      );
+            //@formatter:on
+         throw writeException;
+      }
+   }
+
+   /**
+    * Writes the <code>pojo</code> of class &lt;T&gt; to the <code>file</code> ({@link File}).
+    * 
+    * @param file the file to write the <code>pojo</code> to in JSON.
+    * @param pojo the object of class &lt;T&gt; to be marshaled to JSON.
+    * @throws OseeCoreException when unable to write the <code>file</code> of JSON marshaling fails.
+    */
+
+   public void write(IFile iFile, T pojo) {
+
+      try {
+
+         BufferByteArrayOutputStream outputStream = new BufferByteArrayOutputStream();
+
+         this.objectMapper.writeValue(outputStream, pojo);
+
+         InputStream inputStream = new ByteArrayInputStream(outputStream.getBuffer(), 0, outputStream.size());
+
+         if (iFile.exists()) {
+            iFile.setContents(inputStream, 0, null);
+         } else {
+            iFile.create(inputStream, 0, null);
+         }
+
+      } catch (Exception e) {
+         //@formatter:off
+            OseeCoreException writeException =
+               new OseeCoreException
+                      (
+                           "Failed to write JSON file."                                                                        + "\n"
+                         + "   File Description: " + this.fileDescription                                                      + "\n"
+                         + "   File:             " + ( Objects.isNull( iFile ) ? "(null)" : iFile.getLocation().toOSString() ) + "\n",
+                         e
+                      );
+            //@formatter:on
+         throw writeException;
+      }
+   };
+
+}
diff --git a/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/util/Projects.java b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/util/Projects.java
new file mode 100644
index 0000000..ea74fe4
--- /dev/null
+++ b/org.eclipse.ote.cat.plugin/src/org/eclipse/ote/cat/plugin/util/Projects.java
@@ -0,0 +1,401 @@
+/*********************************************************************
+ * Copyright (c) 2024 Boeing
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ * 
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors: Boeing - initial API and implementation
+ **********************************************************************/
+
+package org.eclipse.ote.cat.plugin.util;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.osee.framework.jdk.core.type.OseeCoreException;
+import org.eclipse.osee.framework.jdk.core.type.Pair;
+
+/**
+ * A class of static utility methods for {@link IProject} operations.
+ * 
+ * @author Loren K. Ashley
+ */
+
+public class Projects {
+
+   /**
+    * Adds the project nature specified by <code>natureIdentifier</code> to the start of the <code>project</code>'s
+    * nature list. When the <code>project</code> is closed, <code>project</code> is <code>null</code>, or
+    * <code>natureIdentifier</code> is <code>null</code> no action is performed.
+    * 
+    * @param project the <code>project</code> to add the nature to.
+    * @param natureIdentifier the identifier of the project nature to add.
+    * @throws OseeCoreException when:
+    * <ul>
+    * <li>unable to obtain the project's list of natures,</li>
+    * <li>the modified list of natures fails to validate with {@link IWorkspace#validateNatureSet}, or</li>
+    * <li>unable to set the project's list of natures.</li>
+    * </ul>
+    */
+
+   public static void addNature(IProject project, String natureIdentifier) {
+      if (Objects.isNull(project) || !project.isOpen() || Objects.isNull(natureIdentifier)) {
+         return;
+      }
+      IProjectDescription projectDescription = Projects.getProjectDescription(project);
+      String[] natures = projectDescription.getNatureIds();
+      String[] newNatures = new String[natures.length + 1];
+      System.arraycopy(natures, 0, newNatures, 1, natures.length);
+      newNatures[0] = natureIdentifier;
+      Projects.validateNatureSet(newNatures);
+      projectDescription.setNatureIds(newNatures);
+      Projects.setProjectDescription(project, projectDescription);
+   }
+
+   /**
+    * A wrapper on the method {@link IProject}<code>::getDescription</code> that catches any checked exceptions and
+    * throws them as a runtime {@link OseeCoreException}.
+    * 
+    * @param project the {@link IProject} to get the {@link IProjectDescription} from.
+    * @return the <code>project</code>'s {@link IProjectDescription}.
+    * @throws OseeCoreException when unable to get the <code>projectDescription</code> from the <code>project</code>.
+    */
+
+   public static IProjectDescription getProjectDescription(IProject project) {
+      try {
+         IProjectDescription projectDescription = project.getDescription();
+         return projectDescription;
+      } catch (Exception e) {
+         //@formatter:off
+         org.eclipse.osee.framework.jdk.core.type.OseeCoreException getNatureListException =
+            new OseeCoreException
+                   (
+                        "Failed to obtain the \"IProjectDescription\" for a project." + "\n"
+                      + "Project: " + project,
+                      e
+                   );
+         //@formatter:on
+         throw getNatureListException;
+      }
+   }
+
+   /**
+    * Generates a map of the projects in the workspace.
+    * 
+    * @param <K> The type of the map key.
+    * @param keyExtractor a {@link Function} used to generate a map key for each {@link IProject}.
+    * @return a {@link Map} of the {@link IProject}s in the workspace.
+    * @throws OseeCoreException when unable to obtain the projects from the workspace.
+    */
+
+   public static <K> Map<K, IProject> getProjectMap(Function<IProject, K> keyExtractor) {
+      Map<K, IProject> map = new HashMap<>();
+      IProject[] projects = Projects.getProjects();
+      for (int i = 0; i < projects.length; i++) {
+         IProject project = projects[i];
+         K key = keyExtractor.apply(projects[i]);
+         map.put(key, project);
+      }
+      return map;
+   }
+
+   /**
+    * Generates a pair of project maps from the workspace. One map contains all of the open projects with the specified
+    * project nature and the other map contains all of the open projects without the project nature.
+    * 
+    * @param <K> The type of the map key.
+    * @param natureIdentifier the identifier of the project nature to separate the workspace projects with.
+    * @param keyExtractor a {@link Function} used to generate a map key for each {@link IProject}.
+    * @return a {@link Pair} with the first element being a {@link Map} of the open {@link IProject}s with the nature
+    * specified by <code>natureIdentifier</code> and the second element being a {@link Map} of the open
+    * {@link IProject}s without the nature.
+    * @throws OseeCoreException when unable to obtain the projects from the workspace.
+    */
+
+   public static <C> Pair<C, C> getProjectsForNature(String natureIdentifier, Supplier<C> containerFactory, BiConsumer<C, IProject> containerAdder) {
+      C projectsWithNature = containerFactory.get();
+      C projectsWithoutNature = containerFactory.get();
+      Pair<C, C> pair = new Pair<>(projectsWithNature, projectsWithoutNature);
+      IProject[] projectArray = Projects.getProjects();
+      for (IProject project : projectArray) {
+         if (!project.isOpen()) {
+            continue;
+         }
+         if (Projects.hasNature(project, natureIdentifier)) {
+            containerAdder.accept(projectsWithNature, project);
+         } else {
+            containerAdder.accept(projectsWithoutNature, project);
+         }
+      }
+      return pair;
+   }
+
+   /**
+    * Gets an array of the {@link IProject}s in the workspace.
+    * 
+    * @return an array of the {@link IProject}s from the workspace.
+    * @throws OseeCoreException when unable to obtain the workspace projects.
+    */
+
+   public static IProject[] getProjects() {
+      try {
+         IWorkspace workspace = ResourcesPlugin.getWorkspace();
+         IWorkspaceRoot workSpaceRoot = workspace.getRoot();
+         IProject[] projectArray = workSpaceRoot.getProjects();
+         return projectArray;
+      } catch (Exception e) {
+         //@formatter:off
+         OseeCoreException getProjectsException =
+            new OseeCoreException
+                   (
+                      "Failed to get projects from the workspace."
+                   );
+         //@formatter:on
+         throw getProjectsException;
+      }
+   }
+
+   /**
+    * Gets a list of open projects in the workspace with the nature specified by <code>natureIdentifier</code>.
+    * 
+    * @param natureIdentifier only projects that have a nature with this identifier are added to the list.
+    * @return a {@link LinkedList} of the workspace {@link IProject}s with the nature specified by
+    * <code>natureIdentifier</code>. When no projects are found with the nature, an empty {@link LinkedList} is
+    * returned.
+    * @throws OseeCoreException when:
+    * <ul>
+    * <li>Unable to obtain the workspace projects.</li>
+    * <li>Unable to determine if a project has a nature.</li>
+    * </ul>
+    */
+
+   public static LinkedList<IProject> getProjectsWithNature(String natureIdentifier) {
+      LinkedList<IProject> projectsWithNature = new LinkedList<>();
+      IProject[] projectArray = Projects.getProjects();
+      for (IProject project : projectArray) {
+         if (Projects.hasNature(project, natureIdentifier)) {
+            projectsWithNature.add(project);
+         }
+      }
+      return projectsWithNature;
+   }
+
+   /**
+    * Gets a list of open projects in the workspace that do not have the nature specified by
+    * <code>natureIdentifier</code>.
+    * 
+    * @param natureIdentifier only projects that do not have a nature with this identifier are added to the list.
+    * @return a {@link LinkedList} of the workspace {@link IProject}s that do not have the nature specified by
+    * <code>natureIdentifier</code>. When no projects are found without the nature, an empty {@link LinkedList} is
+    * returned.
+    * @throws OseeCoreException when:
+    * <ul>
+    * <li>Unable to obtain the workspace projects.</li>
+    * <li>Unable to determine if a project has a nature.</li>
+    * </ul>
+    */
+
+   public static LinkedList<IProject> getProjectsWithoutNature(String natureIdentifier) {
+      LinkedList<IProject> projectsWithoutNature = new LinkedList<>();
+      IProject[] projectArray = Projects.getProjects();
+      for (IProject project : projectArray) {
+         if (!Projects.hasNature(project, natureIdentifier)) {
+            projectsWithoutNature.add(project);
+         }
+      }
+      return projectsWithoutNature;
+   }
+
+   /**
+    * A {@link Predicate} to determine if a project has a nature.
+    * 
+    * @param project the {@link IProject} to be tested.
+    * @param natureIdentifier the identifier of the nature to test for.
+    * @return <code>true</code> when the {@link IProject} has the nature specified by <code>natureIdentifier</code>;
+    * otherwise, <code>false</code>.
+    */
+
+   public static boolean hasNature(IProject project, String natureIdentifier) {
+      try {
+         return project.isOpen() && project.hasNature(natureIdentifier);
+      } catch (Exception e) {
+         //@formatter:off
+         OseeCoreException cannotDetermineNatureException =
+            new OseeCoreException
+                   (
+                        "Failed to determine if project has the specified nature." + "\n"
+                      + "   Project: " + project                                   + "\n"
+                      + "   Nature:  " + natureIdentifier                          + "\n"
+                   );
+         //@formatter:on
+         throw cannotDetermineNatureException;
+      }
+   }
+
+   /**
+    * Moves the project nature at the position <code>naturePosition</code> to the start of the <code>project</code>'s
+    * nature list.
+    * 
+    * @param project the project to modify.
+    * @param naturePosition the current list position of the project nature to be moved to the start of the project's
+    * nature list.
+    * @throws OseeCoreException when:
+    * <ul>
+    * <li>unable to obtain the project's list of natures,</li>
+    * <li>the modified list of natures fails to validate with {@link IWorkspace#validateNatureSet}, or</li>
+    * <li>unable to set the project's list of natures.</li>
+    * </ul>
+    */
+
+   public static void moveNatureToTheFirstPosition(IProject project, int naturePosition) {
+      IProjectDescription projectDescription = Projects.getProjectDescription(project);
+      String[] natures = projectDescription.getNatureIds();
+      String[] newNatures = new String[natures.length + 1];
+      System.arraycopy(natures, 0, newNatures, 1, naturePosition);
+      System.arraycopy(natures, naturePosition + 1, newNatures, naturePosition + 1, natures.length - naturePosition);
+      newNatures[0] = natures[naturePosition];
+      Projects.validateNatureSet(newNatures);
+      projectDescription.setNatureIds(newNatures);
+      Projects.setProjectDescription(project, projectDescription);
+   }
+
+   /**
+    * Finds the position of the project nature specified with <code>natureIdentifier</code> in the
+    * <code>project</code>'s list of natures. When the nature is not in the project's nature list, the
+    * <code>project</code> is not open, or <code>project</code> is <code>null</code> -1 is returned.
+    * 
+    * @param project the {@link IProject} to find the nature position.
+    * @param natureIdentifier the identifier of the nature to determine the position of.
+    * @return when the <code>project</code>'s nature list contains nature specified by <code>natureIdentifier</code>,
+    * the index position of the nature in the nature list; otherwise, -1.
+    * @throws OseeCoreException when unable to obtain the project's list of natures.
+    */
+
+   public static int position(IProject project, String natureIdentifier) {
+      if (Objects.isNull(project) || !project.isOpen()) {
+         return -1;
+      }
+      try {
+         IProjectDescription projectDescription = project.getDescription();
+         String[] natures = projectDescription.getNatureIds();
+         for (int i = 0; i < natures.length; i++) {
+            if (natureIdentifier.equals(natures[i])) {
+               return i;
+            }
+         }
+         return -1;
+      } catch (Exception e) {
+         //@formatter:off
+         OseeCoreException natureListPositionException =
+            new OseeCoreException
+                   (
+                        "Failed to obtain the natures list for a project." + "\n"
+                      + "Project: " + project                              + "\n",
+                      e
+                   );
+         //@formatter:on
+         throw natureListPositionException;
+      }
+   }
+
+   /**
+    * Removes the nature specified by <code>natureIdentifier</code> from the <code>project</code>.
+    * 
+    * @param project the {@link IProject} to remove the specified nature from.
+    * @param natureIdentifier the nature to remove from the {@link IProject}.
+    * @throws OseeCoreException when:
+    * <ul>
+    * <li>Unable to get or set the <code>project</code>'s {@link IProjectDescription}.</li>
+    * <li>The <code>project</code>'s nature set with the nature removed fails to validate.</li>
+    * </ul>
+    */
+
+   public static void removeProjectNature(IProject project, String natureIdentifier) {
+      int position = Projects.position(project, natureIdentifier);
+      if (position < 0) {
+         return;
+      }
+      IProjectDescription projectDescription = Projects.getProjectDescription(project);
+      String[] natures = projectDescription.getNatureIds();
+      String[] newNatures = new String[natures.length - 1];
+      System.arraycopy(natures, 0, newNatures, 0, position);
+      System.arraycopy(natures, position + 1, newNatures, position, natures.length - position - 1);
+      Projects.validateNatureSet(newNatures);
+      projectDescription.setNatureIds(newNatures);
+      Projects.setProjectDescription(project, projectDescription);
+   }
+
+   /**
+    * A wrapper on the method {@link IProject}<code>::setDescription</code> that catches any checked exceptions and
+    * throws them as a runtime {@link OseeCoreException}.
+    * 
+    * @param project the {@link IProject} to set the {@link IProjectDescription} for.
+    * @param projectDescription the {@link IProjectDescription} to be applied to the <code>project</code>.
+    * @throws OseeCoreException when unable to apply the <code>projectDescription</code> to the <code>project</code>.
+    */
+
+   public static void setProjectDescription(IProject project, IProjectDescription projectDescription) {
+      try {
+         project.setDescription(projectDescription, null);
+      } catch (Exception e) {
+         //@formatter:off
+         OseeCoreException setNatureListPositionException =
+            new OseeCoreException
+                   (
+                        "Failed to set the project description." + "\n"
+                      + "Project: " + project                    + "\n",
+                      e
+                   );
+         //@formatter:on
+         throw setNatureListPositionException;
+      }
+   }
+
+   /**
+    * Validates that all project natures in the array <code>natureSet</code> are compatible according to the workspace.
+    * 
+    * @param natureSet the set of project natures to be tested.
+    * @throws OseeCoreException when the project natures in <code>natureSet</code> are not compatible.
+    */
+
+   public static void validateNatureSet(String[] natureSet) {
+      IWorkspace workspace = ResourcesPlugin.getWorkspace();
+      IStatus status = workspace.validateNatureSet(natureSet);
+      if (status.getCode() != IStatus.OK) {
+         //@formatter:off
+         OseeCoreException natureListPositionException =
+            new OseeCoreException
+                   (
+                        "Project nature set is not compatabile." + "\n"
+                      + "   Project Natures: "                   + "\n"
+                      + Arrays.stream( natureSet ).collect( Collectors.joining( "      ", ",\n      ", "\n")),
+                      status
+                   );
+         //@formatter:on
+         throw natureListPositionException;
+      }
+   }
+
+   /**
+    * Constructor is private to prevent instantiation of the class.
+    */
+
+   private Projects() {
+   }
+
+}