class StepContext extends SynchronizedAttributeAccessor {

	private final StepExecution stepExecution;

	private final Map<String, Set<Runnable>> callbacks = new HashMap<>();

	/**
	 * Create a new instance of {@link StepContext} for this {@link StepExecution}.
	 * @param stepExecution a step execution
	 */
	public StepContext(StepExecution stepExecution) {
		super();
		Assert.notNull(stepExecution, "A StepContext must have a non-null StepExecution");
		this.stepExecution = stepExecution;
	}

	/**
	 * Convenient accessor for current step name identifier. Usually this is the same as
	 * the bean name of the step that is executing (but might not be e.g. in a partition).
	 * @return the step name identifier of the current {@link StepExecution}
	 */
	public String getStepName() {
		return stepExecution.getStepName();
	}

	/**
	 * Convenient accessor for current job name identifier.
	 * @return the job name identifier of the enclosing {@link JobInstance} associated
	 * with the current {@link StepExecution}
	 */
	public String getJobName() {
		Assert.state(stepExecution.getJobExecution() != null, "StepExecution does not have a JobExecution");
		Assert.state(stepExecution.getJobExecution().getJobInstance() != null,
				"StepExecution does not have a JobInstance");
		return stepExecution.getJobExecution().getJobInstance().getJobName();
	}

	/**
	 * Convenient accessor for current {@link JobInstance} identifier.
	 * @return the identifier of the enclosing {@link JobInstance} associated with the
	 * current {@link StepExecution}
	 */
	public Long getJobInstanceId() {<FILL_FUNCTION_BODY>}

	/**
	 * Convenient accessor for System properties to make it easy to access them from
	 * placeholder expressions.
	 * @return the current System properties
	 */
	public Properties getSystemProperties() {
		return System.getProperties();
	}

	/**
	 * @return a map containing the items from the step {@link ExecutionContext}
	 */
	public Map<String, Object> getStepExecutionContext() {
		return stepExecution.getExecutionContext().toMap();
	}

	/**
	 * @return a map containing the items from the job {@link ExecutionContext}
	 */
	public Map<String, Object> getJobExecutionContext() {
		return stepExecution.getJobExecution().getExecutionContext().toMap();
	}

	/**
	 * @return a map containing the items from the {@link JobParameters}
	 */
	public Map<String, Object> getJobParameters() {
		Map<String, Object> result = new HashMap<>();
		for (Entry<String, JobParameter<?>> entry : stepExecution.getJobParameters().getParameters().entrySet()) {
			result.put(entry.getKey(), entry.getValue().getValue());
		}
		return Collections.unmodifiableMap(result);
	}

	/**
	 * Allow clients to register callbacks for clean up on close.
	 * @param name the callback id (unique attribute key in this context)
	 * @param callback a callback to execute on close
	 */
	public void registerDestructionCallback(String name, Runnable callback) {
		synchronized (callbacks) {
			Set<Runnable> set = callbacks.computeIfAbsent(name, k -> new HashSet<>());
			set.add(callback);
		}
	}

	private void unregisterDestructionCallbacks(String name) {
		synchronized (callbacks) {
			callbacks.remove(name);
		}
	}

	/**
	 * Override base class behaviour to ensure destruction callbacks are unregistered as
	 * well as the default behaviour.
	 *
	 * @see SynchronizedAttributeAccessor#removeAttribute(String)
	 */
	@Override
	@Nullable
	public Object removeAttribute(String name) {
		unregisterDestructionCallbacks(name);
		return super.removeAttribute(name);
	}

	/**
	 * Clean up the context at the end of a step execution. Must be called once at the end
	 * of a step execution to honour the destruction callback contract from the
	 * {@link StepScope}.
	 */
	public void close() {

		List<Exception> errors = new ArrayList<>();

		Map<String, Set<Runnable>> copy = Collections.unmodifiableMap(callbacks);

		for (Entry<String, Set<Runnable>> entry : copy.entrySet()) {
			Set<Runnable> set = entry.getValue();
			for (Runnable callback : set) {
				if (callback != null) {
					/*
					 * The documentation of the interface says that these callbacks must
					 * not throw exceptions, but we don't trust them necessarily...
					 */
					try {
						callback.run();
					}
					catch (RuntimeException t) {
						errors.add(t);
					}
				}
			}
		}

		if (errors.isEmpty()) {
			return;
		}

		Exception error = errors.get(0);
		if (error instanceof RuntimeException) {
			throw (RuntimeException) error;
		}
		else {
			throw new UnexpectedJobExecutionException(
					"Could not close step context, rethrowing first of " + errors.size() + " exceptions.", error);
		}
	}

	/**
	 * The current {@link StepExecution} that is active in this context.
	 * @return the current {@link StepExecution}
	 */
	public StepExecution getStepExecution() {
		return stepExecution;
	}

	/**
	 * @return unique identifier for this context based on the step execution
	 */
	public String getId() {
		Assert.state(stepExecution.getId() != null,
				"StepExecution has no id.  " + "It must be saved before it can be used in step scope.");
		return "execution#" + stepExecution.getId();
	}

	/**
	 * Extend the base class method to include the step execution itself as a key (i.e.
	 * two contexts are only equal if their step executions are the same).
	 *
	 * @see SynchronizedAttributeAccessor#equals(Object)
	 */
	@Override
	public boolean equals(Object other) {
		if (!(other instanceof StepContext context)) {
			return false;
		}
		if (other == this) {
			return true;
		}
		if (context.stepExecution == stepExecution) {
			return true;
		}
		return stepExecution.equals(context.stepExecution);
	}

	/**
	 * Overrides the default behaviour to provide a hash code based only on the step
	 * execution.
	 *
	 * @see SynchronizedAttributeAccessor#hashCode()
	 */
	@Override
	public int hashCode() {
		return stepExecution.hashCode();
	}

	@Override
	public String toString() {
		return super.toString() + ", stepExecutionContext=" + getStepExecutionContext() + ", jobExecutionContext="
				+ getJobExecutionContext() + ", jobParameters=" + getJobParameters();
	}

}

		Assert.state(stepExecution.getJobExecution() != null, "StepExecution does not have a JobExecution");
		Assert.state(stepExecution.getJobExecution().getJobInstance() != null,
				"StepExecution does not have a JobInstance");
		return stepExecution.getJobExecution().getJobInstance().getInstanceId();
	
<methods>public non-sealed void <init>() ,public java.lang.String[] attributeNames() ,public boolean equals(java.lang.Object) ,public java.lang.Object getAttribute(java.lang.String) ,public boolean hasAttribute(java.lang.String) ,public int hashCode() ,public java.lang.Object removeAttribute(java.lang.String) ,public void setAttribute(java.lang.String, java.lang.Object) ,public java.lang.Object setAttributeIfAbsent(java.lang.String, java.lang.Object) ,public java.lang.String toString() <variables>org.springframework.core.AttributeAccessorSupport support