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 "plugin.xml" 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 "plugin.xml" 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 "initializer" configuration element is not found; or the "option" 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<T> 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<T> 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<T> 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<T> 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 "OSEE-INF" 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 "OSEE-INF" 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 "plugin.xml" 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); + } + + /** + * "Updates" 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>".project" 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 <T>. + */ + + 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 <T>. + * + * @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 <T>. + * + * @param file the file to be read. + * @return an object of class <T> 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 <T>. + * + * @param file the file to be read. + * @return an object of class <T> 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 <T>. + * + * @param inputStream the {@link InputStream} to read from. + * @return an object of class <T> 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 <T> 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 <T> 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 <T> 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 <T> 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() { + } + +}