JobDataMap.java

/* 
 * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
 * Copyright IBM Corp. 2024, 2025
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
 * use this file except in compliance with the License. You may obtain a copy 
 * of the License at 
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *   
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
 * License for the specific language governing permissions and limitations 
 * under the License.
 * 
 */

package org.quartz;

import java.io.Serializable;
import java.util.Map;

import org.quartz.utils.StringKeyDirtyFlagMap;

/**
 * Holds state information for <code>Job</code> instances.
 * 
 * <p>
 * <code>JobDataMap</code> instances are stored once when the <code>Job</code>
 * is added to a scheduler. They are also re-persisted after every execution of
 * jobs annotated with <code>@PersistJobDataAfterExecution</code>.
 * </p>
 * 
 * <p>
 * <code>JobDataMap</code> instances can also be stored with a 
 * <code>Trigger</code>.  This can be useful in the case where you have a Job
 * that is stored in the scheduler for regular/repeated use by multiple 
 * Triggers, yet with each independent triggering, you want to supply the
 * Job with different data inputs.  
 * </p>
 * 
 * <p>
 * The <code>JobExecutionContext</code> passed to a Job at execution time 
 * also contains a convenience <code>JobDataMap</code> that is the result
 * of merging the contents of the trigger's JobDataMap (if any) over the
 * Job's JobDataMap (if any).  
 * </p>
 *
 * <p>
 * Update since 2.2.4 - We keep an dirty flag for this map so that whenever you modify(add/delete) any of the entries,
 * it will set to "true". However if you create new instance using an existing map with {@link #JobDataMap(Map)}, then
 * the dirty flag will NOT be set to "true" until you modify the instance.
 * </p>
 * 
 * @see Job
 * @see PersistJobDataAfterExecution
 * @see Trigger
 * @see JobExecutionContext
 * 
 * @author James House
 */
public class JobDataMap extends StringKeyDirtyFlagMap implements Serializable {

    private static final long serialVersionUID = -6939901990106713909L;
    
    /*
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * 
     * Data members.
     * 
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */

    /**
     * <p>
     * Create an empty <code>JobDataMap</code>.
     * </p>
     */
    public JobDataMap() {
        super(15);
    }

    /**
     * <p>
     * Create a <code>JobDataMap</code> with the given data.
     * </p>
     */
    public JobDataMap(Map<?, ?> map) {
        this();
        @SuppressWarnings("unchecked") // casting to keep API compatible and avoid compiler errors/warnings.
        Map<String, Object> mapTyped = (Map<String, Object>)map;
        putAll(mapTyped);

        // When constructing a new data map from another existing map, we should NOT mark dirty flag as true
        // Use case: loading JobDataMap from DB
        clearDirtyFlag();
    }

    /*
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * 
     * Interface.
     * 
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */

    /**
     * <p>
     * Adds the given <code>boolean</code> value as a string version to the
     * <code>Job</code>'s data map.
     * </p>
     */
    public void putAsString(String key, boolean value) {
        String strValue = Boolean.valueOf(value).toString();

        super.put(key, strValue);
    }

    /**
     * <p>
     * Adds the given <code>Boolean</code> value as a string version to the
     * <code>Job</code>'s data map.
     * </p>
     */
    public void putAsString(String key, Boolean value) {
        String strValue = value.toString();

        super.put(key, strValue);
    }

    /**
     * <p>
     * Adds the given <code>char</code> value as a string version to the
     * <code>Job</code>'s data map.
     * </p>
     */
    public void putAsString(String key, char value) {
        String strValue = Character.valueOf(value).toString();

        super.put(key, strValue);
    }

    /**
     * <p>
     * Adds the given <code>Character</code> value as a string version to the
     * <code>Job</code>'s data map.
     * </p>
     */
    public void putAsString(String key, Character value) {
        String strValue = value.toString();

        super.put(key, strValue);
    }

    /**
     * <p>
     * Adds the given <code>double</code> value as a string version to the
     * <code>Job</code>'s data map.
     * </p>
     */
    public void putAsString(String key, double value) {
        String strValue = Double.toString(value);

        super.put(key, strValue);
    }

    /**
     * <p>
     * Adds the given <code>Double</code> value as a string version to the
     * <code>Job</code>'s data map.
     * </p>
     */
    public void putAsString(String key, Double value) {
        String strValue = value.toString();

        super.put(key, strValue);
    }

    /**
     * <p>
     * Adds the given <code>float</code> value as a string version to the
     * <code>Job</code>'s data map.
     * </p>
     */
    public void putAsString(String key, float value) {
        String strValue = Float.toString(value);

        super.put(key, strValue);
    }

    /**
     * <p>
     * Adds the given <code>Float</code> value as a string version to the
     * <code>Job</code>'s data map.
     * </p>
     */
    public void putAsString(String key, Float value) {
        String strValue = value.toString();

        super.put(key, strValue);
    }

    /**
     * <p>
     * Adds the given <code>int</code> value as a string version to the
     * <code>Job</code>'s data map.
     * </p>
     */
    public void putAsString(String key, int value) {
        String strValue = Integer.valueOf(value).toString();

        super.put(key, strValue);
    }

    /**
     * <p>
     * Adds the given <code>Integer</code> value as a string version to the
     * <code>Job</code>'s data map.
     * </p>
     */
    public void putAsString(String key, Integer value) {
        String strValue = value.toString();

        super.put(key, strValue);
    }

    /**
     * <p>
     * Adds the given <code>long</code> value as a string version to the
     * <code>Job</code>'s data map.
     * </p>
     */
    public void putAsString(String key, long value) {
        String strValue = Long.valueOf(value).toString();

        super.put(key, strValue);
    }

    /**
     * <p>
     * Adds the given <code>Long</code> value as a string version to the
     * <code>Job</code>'s data map.
     * </p>
     */
    public void putAsString(String key, Long value) {
        String strValue = value.toString();

        super.put(key, strValue);
    }

    /**
     * <p>
     * Retrieve the identified <code>int</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String.
     */
    public int getIntFromString(String key) {
        Object obj = get(key);

        return Integer.parseInt((String) obj);
    }

    /**
     * <p>
     * Retrieve the identified <code>int</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String or Integer.
     */
    public int getIntValue(String key) {
        Object obj = get(key);

        if(obj instanceof String) {
            return getIntFromString(key);
        } else {
            return getInt(key);
        }
    }
    
    /**
     * <p>
     * Retrieve the identified <code>int</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String.
     */
    public Integer getIntegerFromString(String key) {
        Object obj = get(key);

        return Integer.valueOf((String) obj);
    }

    /**
     * <p>
     * Retrieve the identified <code>boolean</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String.
     */
    public boolean getBooleanValueFromString(String key) {
        Object obj = get(key);

        return Boolean.valueOf((String) obj);
    }

    /**
     * <p>
     * Retrieve the identified <code>boolean</code> value from the 
     * <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String or Boolean.
     */
    public boolean getBooleanValue(String key) {
        Object obj = get(key);

        if(obj instanceof String) {
            return getBooleanValueFromString(key);
        } else {
            return getBoolean(key);
        }
    }

    /**
     * <p>
     * Retrieve the identified <code>Boolean</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String.
     */
    public Boolean getBooleanFromString(String key) {
        Object obj = get(key);

        return Boolean.valueOf((String) obj);
    }

    /**
     * <p>
     * Retrieve the identified <code>char</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String.
     */
    public char getCharFromString(String key) {
        Object obj = get(key);

        return ((String) obj).charAt(0);
    }

    /**
     * <p>
     * Retrieve the identified <code>Character</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String.
     */
    public Character getCharacterFromString(String key) {
        Object obj = get(key);

        return ((String) obj).charAt(0);
    }

    /**
     * <p>
     * Retrieve the identified <code>double</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String.
     */
    public double getDoubleValueFromString(String key) {
        Object obj = get(key);

        return Double.valueOf((String) obj);
    }

    /**
     * <p>
     * Retrieve the identified <code>double</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String or Double.
     */
    public double getDoubleValue(String key) {
        Object obj = get(key);

        if(obj instanceof String) {
            return getDoubleValueFromString(key);
        } else {
            return getDouble(key);
        }
    }

    /**
     * <p>
     * Retrieve the identified <code>Double</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String.
     */
    public Double getDoubleFromString(String key) {
        Object obj = get(key);

        return Double.valueOf((String) obj);
    }

    /**
     * <p>
     * Retrieve the identified <code>float</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String.
     */
    public float getFloatValueFromString(String key) {
        Object obj = get(key);

        return Float.parseFloat((String) obj);
    }

    /**
     * <p>
     * Retrieve the identified <code>float</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String or Float.
     */
    public float getFloatValue(String key) {
        Object obj = get(key);

        if(obj instanceof String) {
            return getFloatValueFromString(key);
        } else {
            return getFloat(key);
        }
    }
    
    /**
     * <p>
     * Retrieve the identified <code>Float</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String.
     */
    public Float getFloatFromString(String key) {
        Object obj = get(key);

        return Float.valueOf((String) obj);
    }

    /**
     * <p>
     * Retrieve the identified <code>long</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String.
     */
    public long getLongValueFromString(String key) {
        Object obj = get(key);

        return Long.parseLong((String) obj);
    }

    /**
     * <p>
     * Retrieve the identified <code>long</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String or Long.
     */
    public long getLongValue(String key) {
        Object obj = get(key);

        if(obj instanceof String) {
            return getLongValueFromString(key);
        } else {
            return getLong(key);
        }
    }
    
    /**
     * <p>
     * Retrieve the identified <code>Long</code> value from the <code>JobDataMap</code>.
     * </p>
     * 
     * @throws ClassCastException
     *           if the identified object is not a String.
     */
    public Long getLongFromString(String key) {
        Object obj = get(key);

        return Long.valueOf((String) obj);
    }
}