Bug 574779: [ToolService] Enhance ToolCommandHandler for better data
access

Change-Id: Ied2238b50a24ee477fd05ee981e72666f91fe52d
diff --git a/jcommons/org.eclipse.statet.jcommons.util/META-INF/MANIFEST.MF b/jcommons/org.eclipse.statet.jcommons.util/META-INF/MANIFEST.MF
index 0a54b80..3f9a5a2 100644
--- a/jcommons/org.eclipse.statet.jcommons.util/META-INF/MANIFEST.MF
+++ b/jcommons/org.eclipse.statet.jcommons.util/META-INF/MANIFEST.MF
@@ -22,6 +22,5 @@
  org.eclipse.statet.jcommons.status.util;version="4.5.0",
  org.eclipse.statet.jcommons.string;version="4.5.0",
  org.eclipse.statet.jcommons.ts.core;version="4.5.0",
- org.eclipse.statet.jcommons.ts.core.util;version="4.5.0",
  org.eclipse.statet.jcommons.util;version="4.5.0"
 Import-Package: org.apache.commons.logging;version="1.2.0";resolution:=optional
diff --git a/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/lang/ObjectUtils.java b/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/lang/ObjectUtils.java
index a2cf102..8b6b01f 100644
--- a/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/lang/ObjectUtils.java
+++ b/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/lang/ObjectUtils.java
@@ -304,10 +304,18 @@
 	@SuppressWarnings("null")
 	public static final Class<@Nullable Object> Nullable_Object_TYPE= Object.class;
 	
+	public static final Class<@NonNull Object[]> NonNull_ObjectArray_TYPE= Object[].class;
+	@SuppressWarnings("null")
+	public static final Class<@Nullable Object[]> Nullable_ObjectArray_TYPE= Object[].class;
+	
 	public static final Class<@NonNull String> NonNull_String_TYPE= String.class;
 	@SuppressWarnings("null")
 	public static final Class<@Nullable String> Nullable_String_TYPE= String.class;
 	
+	public static final Class<@NonNull String[]> NonNull_StringArray_TYPE= String[].class;
+	@SuppressWarnings("null")
+	public static final Class<@Nullable String[]> Nullable_StringArray_TYPE= String[].class;
+	
 	
 	public static boolean isNull(final @Nullable Object object) {
 		return (object == null);
diff --git a/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/BasicToolCommandData.java b/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/BasicToolCommandData.java
new file mode 100644
index 0000000..cda6e8c
--- /dev/null
+++ b/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/BasicToolCommandData.java
@@ -0,0 +1,121 @@
+/*=============================================================================#
+ # Copyright (c) 2021 Stephan Wahlbrink and others.
+ # 
+ # 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, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.jcommons.ts.core;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+
+@NonNullByDefault
+public class BasicToolCommandData implements ToolCommandData {
+	
+	
+	private final Map<String, ?> data;
+	
+	private final Map<String, @Nullable Object> returnData= new HashMap<>();
+	
+	
+	public BasicToolCommandData(final Map<String, ?> data) {
+		this.data= data;
+	}
+	
+	public BasicToolCommandData() {
+		this.data= Map.of();
+	}
+	
+	
+	@Override
+	public @Nullable Object getRawData(final String key) {
+		return this.data;
+	}
+	
+	protected @Nullable Object getData(final String key) {
+		Object data= this.returnData.get(key);
+		if (data == null) {
+			data= getRawData(key);
+		}
+		return data;
+	}
+	
+	@Override
+	@SuppressWarnings("unchecked")
+	public <TData> @Nullable TData get(final String key, final Class<TData> type) {
+		final Object rawData= getData(key);
+		if (rawData != null) {
+			if (type.isInstance(rawData)) {
+				return (TData)rawData;
+			}
+			else {
+				final TData typedData= convert(rawData, type);
+				if (typedData == null) {
+					throw new IllegalArgumentException(String.format("Data entry '%1$s' is incompatible (%2$s).",
+							key, rawData.getClass().getName() ));
+				}
+				return typedData;
+			}
+		}
+		return null;
+	}
+	
+	
+	protected Map<String, @Nullable Object> getReturnData() {
+		return this.returnData;
+	}
+	
+	public boolean hasReturnData() {
+		return !this.returnData.isEmpty();
+	}
+	
+	@Override
+	public void setReturnData(final String key, final @Nullable Object value) {
+		this.returnData.put(key, value);
+	}
+	
+	@Override
+	public void removeReturnData(final String key) {
+		this.returnData.remove(key);
+	}
+	
+	
+	@SuppressWarnings("unchecked")
+	public <TData> @Nullable TData convert(@Nullable Object data, final Class<TData> type) {
+		if (data != null && data.getClass().isArray()
+				&& data.getClass().getComponentType().isAssignableFrom(type) ) {
+			final Object[] array= (Object[])data;
+			if (array.length == 1) {
+				data= array[0];
+			}
+		}
+		if (data == null || type.isInstance(data)) {
+			return (TData)data;
+		}
+		if (type == Integer.class) {
+			if (data instanceof Number) {
+				return (TData)Integer.valueOf(((Number)data).intValue());
+			}
+		}
+		if (type == Double.class ) {
+			if (data instanceof Number) {
+				return (TData)Double.valueOf(((Number)data).doubleValue());
+			}
+		}
+		return null;
+	}
+	
+	
+}
diff --git a/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/ToolCommandData.java b/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/ToolCommandData.java
new file mode 100644
index 0000000..1485cab
--- /dev/null
+++ b/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/ToolCommandData.java
@@ -0,0 +1,155 @@
+/*=============================================================================#
+ # Copyright (c) 2021 Stephan Wahlbrink and others.
+ # 
+ # 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, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.jcommons.ts.core;
+
+import org.eclipse.statet.jcommons.lang.NonNull;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+
+/**
+ * The data for execution of tool commands by a {@link ToolCommandHandler}.
+ */
+@NonNullByDefault
+public interface ToolCommandData {
+	
+	
+	public static class IllegalDataException extends RuntimeException {
+		
+		private static final long serialVersionUID= 1L;
+		
+		
+		public IllegalDataException(final String message) {
+			super(message);
+		}
+		
+		
+	}
+	
+	
+	/**
+	 * Returns the raw data as provided by the tool, if available.
+	 * 
+	 * @param key the key of the data entry.
+	 * @return the data or {@code null} if not available.
+	 */
+	public @Nullable Object getRawData(final String key);
+	
+	/**
+	 * Returns the data, if available.
+	 * 
+	 * <p>If the {@link #getRawData(String) raw data} of the data entry is not available, the method
+	 * returns {@code null}.
+	 * If the raw data is available and not an instance of the requested type, the raw data is
+	 * converted to the requested type. If the conversion fails, the method throws an exception.</p>
+	 * 
+	 * @param key the key of the data entry
+	 * @param type the type of the data
+	 * @return the data or {@code null}
+	 * @throws IllegalDataException if the data is incompatible to the required type
+	 */
+	public <TData> @Nullable TData get(final String key, final Class<TData> type)
+			throws IllegalDataException;
+	
+	/**
+	 * Returns the data or the specified default data.
+	 * 
+	 * <p>If the {@link #getRawData(String) raw data} of the data entry is not available, the method
+	 * returns the specified default data.
+	 * If the raw data is available and not an instance of the requested type, the raw data is
+	 * converted to the requested type. If the conversion fails, the method throws an exception.</p>
+	 * 
+	 * @param key the key of the data entry
+	 * @param type the type of the data
+	 * @param defaultData the data to return if the data entry is missing
+	 * @return the data
+	 * @throws IllegalDataException if the data is incompatible to the required type
+	 */
+	public default <TData> TData get(final String key, final Class<TData> type,
+			final @NonNull TData defaultData)
+			throws IllegalDataException {
+		final var data= get(key, type);
+		if (data == null) {
+			return defaultData;
+		}
+		return data;
+	}
+	
+	/**
+	 * Returns the data.
+	 * 
+	 * <p>If the {@link #getRawData(String) raw data} of the data entry is not available, the method
+	 * throws an exception.
+	 * If the raw data is available and not an instance of the requested type, the raw data is
+	 * converted to the requested type. If the conversion fails, the method throws an exception.</p>
+	 * 
+	 * @param key the key of the data entry
+	 * @param type the type of the data
+	 * @return the data
+	 * @throws IllegalDataException if the data is missing or incompatible to the required type
+	 */
+	public default <TData> TData getRequired(final String key, final Class<TData> type)
+			throws IllegalDataException {
+		final var data= get(key, type);
+		if (data == null) {
+			throw new IllegalDataException(String.format("Data entry '%1$s' is missing.",
+					key ));
+		}
+		return data;
+	}
+	
+	
+	public default @Nullable String getString(final String key)
+			throws IllegalDataException {
+		return get(key, String.class);
+	}
+	
+	public default String getString(final String key, final String defaultData)
+			throws IllegalDataException {
+		return get(key, String.class, defaultData);
+	}
+	
+	public default String getStringRequired(final String key)
+			throws IllegalDataException {
+		return getRequired(key, String.class);
+	}
+	
+	public default boolean getBoolean(final String key, final boolean defaultData)
+			throws IllegalDataException {
+		return get(key, Boolean.class, defaultData);
+	}
+	
+	public default boolean getBooleanRequired(final String key)
+			throws IllegalDataException {
+		return getRequired(key, Boolean.class);
+	}
+	
+	public default int getInt(final String key, final int defaultData)
+			throws IllegalDataException {
+		return get(key, Integer.class, defaultData);
+	}
+	
+	public default int getIntRequired(final String key)
+			throws IllegalDataException {
+		return getRequired(key, Integer.class);
+	}
+	
+	
+	public void setReturnData(final String key, @Nullable Object value);
+	
+	public void removeReturnData(final String key);
+	
+	
+}
diff --git a/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/ToolCommandHandler.java b/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/ToolCommandHandler.java
index c461d8e..9aa7a54 100644
--- a/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/ToolCommandHandler.java
+++ b/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/ToolCommandHandler.java
@@ -14,8 +14,6 @@
 
 package org.eclipse.statet.jcommons.ts.core;
 
-import java.util.Map;
-
 import org.eclipse.statet.jcommons.lang.NonNullByDefault;
 import org.eclipse.statet.jcommons.status.ProgressMonitor;
 import org.eclipse.statet.jcommons.status.Status;
@@ -39,13 +37,13 @@
 	 * Executes the specified command
 	 * 
 	 * @param id the ID of the command
-	 * @param service the tool service calling the action
-	 * @param data the action data for input (parameters) and output (return values)
+	 * @param service the tool service calling the command
+	 * @param data the command data for input (parameters) and output (return values)
 	 * @param m the progress monitor
 	 * 
 	 * @return the status
 	 */
-	Status execute(final String id, final ToolService service, final Map<String, Object> data,
+	Status execute(final String id, final ToolService service, final ToolCommandData data,
 			final ProgressMonitor m) throws StatusException;
 	
 }
diff --git a/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/util/ToolCommandHandlerUtils.java b/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/util/ToolCommandHandlerUtils.java
deleted file mode 100644
index 514bc85..0000000
--- a/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/ts/core/util/ToolCommandHandlerUtils.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*=============================================================================#
- # Copyright (c) 2008, 2021 Stephan Wahlbrink and others.
- # 
- # 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, or the Apache License, Version 2.0
- # which is available at https://www.apache.org/licenses/LICENSE-2.0.
- # 
- # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
- # 
- # Contributors:
- #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
- #=============================================================================*/
-
-package org.eclipse.statet.jcommons.ts.core.util;
-
-import java.util.Map;
-
-
-/**
- * Utilities for implementations of {@link org.eclipse.statet.jcommons.ts.core.ToolCommandHandler}.
- */
-public class ToolCommandHandlerUtils {
-	
-	
-	@SuppressWarnings("unchecked")
-	public static <C> C getCheckedData(final Map<String, Object> data, final String name,
-			final Class<C> clazz, final boolean required) {
-		final Object obj= data.get(name);
-		if (obj == null) {
-			if (required) {
-				throw new IllegalArgumentException("missing data entry: '" + name + '"');
-			}
-			return null;
-		}
-		if (!clazz.isInstance(obj)) {
-			throw new IllegalArgumentException("incompatible data entry: '" + name + "' (" + obj.getClass().getName() + ")");
-		}
-		return (C) obj;
-	}
-	
-	@SuppressWarnings("unchecked")
-	public static <C> C getCheckedData(final Map<String, Object> data, final String name, final C defValue) {
-		final Object obj= data.get(name);
-		if (obj == null) {
-			return defValue;
-		}
-		if (!defValue.getClass().isInstance(obj)) {
-			throw new IllegalArgumentException("incompatible data entry: '" + name + "' (" + obj.getClass().getName() + ")");
-		}
-		return (C) obj;
-	}
-	
-	
-	private ToolCommandHandlerUtils() {}
-	
-}