/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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.apache.tools.ant.types.selectors.modifiedselector;


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.nio.file.Files;
import java.util.Iterator;
import java.util.Properties;


/**
 * Use java.util.Properties for storing the values.
 * The use of this Cache-implementation requires the use of the parameter
 * &lt;param name="cache.cachefile" .../&gt; for defining, where to store the
 * properties file.
 *
 * The ModifiedSelector sets the <i>cachefile</i> to the default value
 * <i>cache.properties</i>.
 *
 * Supported &lt;param&gt;s are:
 * <table>
 * <caption>Cache parameters</caption>
 * <tr>
 *   <th>name</th><th>values</th><th>description</th><th>required</th>
 * </tr>
 * <tr>
 *   <td>cache.cachefile</td>
 *   <td><i>path to file</i></td>
 *   <td>the name of the properties file</td>
 *   <td>yes</td>
 * </tr>
 * </table>
 *
 * @version 2003-09-13
 * @since  Ant 1.6
 */
public class PropertiesfileCache implements Cache {


    // -----  member variables - configuration  -----


    /** Where to store the properties? */
    private File cachefile = null;

    /** Object for storing the key-value-pairs. */
    private Properties cache = new Properties();


    // -----  member variables - internal use  -----


    /** Is the cache already loaded? Prevents from multiple load operations. */
    private boolean cacheLoaded = false;

    /** Must the cache be saved? Prevents from multiple save operations. */
    private boolean cacheDirty  = true;


    // -----  Constructors  -----


    /** Bean-Constructor. */
    public PropertiesfileCache() {
    }

    /**
     * Constructor.
     * @param cachefile set the cachefile
     */
    public PropertiesfileCache(File cachefile) {
        this.cachefile = cachefile;
    }


    // -----  Cache-Configuration  -----


    /**
     * Setter.
     * @param file new value
     */
    public void setCachefile(File file) {
        cachefile = file;
    }


    /**
     * Getter.
     * @return the cachefile
     */
    public File getCachefile() {
        return cachefile;
    }

    /**
     * This cache is valid if the cachefile is set.
     * @return true if all is ok false otherwise
     */
    @Override
    public boolean isValid() {
        return (cachefile != null);
    }


    // -----  Data Access


    /**
     * Load the cache from underlying properties file.
     */
    @Override
    public void load() {
        if (cachefile != null && cachefile.isFile() && cachefile.canRead()) {
            try (BufferedInputStream bis = new BufferedInputStream(
                Files.newInputStream(cachefile.toPath()))) {
                cache.load(bis);
            } catch (Exception e) {
                e.printStackTrace(); //NOSONAR
            }
        }
        // after loading the cache is up to date with the file
        cacheLoaded = true;
        cacheDirty  = false;
    }

    /**
     * Saves modification of the cache.
     * Cache is only saved if there is one ore more entries.
     * Because entries can not be deleted by this API, this Cache
     * implementation checks the existence of entries before creating the file
     * for performance optimisation.
     */
    @Override
    public void save() {
        if (!cacheDirty) {
            return;
        }
        if (cachefile != null && cache.propertyNames().hasMoreElements()) {
            try (BufferedOutputStream bos = new BufferedOutputStream(
                Files.newOutputStream(cachefile.toPath()))) {
                cache.store(bos, null);
                bos.flush();
            } catch (Exception e) {
                e.printStackTrace(); //NOSONAR
            }
        }
        cacheDirty = false;
    }

    /** Deletes the cache and its underlying file. */
    @Override
    public void delete() {
        cache = new Properties();
        cachefile.delete();
        cacheLoaded = true;
        cacheDirty = false;
    }

    /**
     * Returns a value for a given key from the cache.
     * @param key the key
     * @return the stored value
     */
    @Override
    public Object get(Object key) {
        if (!cacheLoaded) {
            load();
        }
        try {
            return cache.getProperty(String.valueOf(key));
        } catch (ClassCastException e) {
            return null;
        }
    }

    /**
     * Saves a key-value-pair in the cache.
     * @param key the key
     * @param value the value
     */
    @Override
    public void put(Object key, Object value) {
        cache.put(String.valueOf(key), String.valueOf(value));
        cacheDirty = true;
    }

    /**
     * Returns an iterator over the keys in the cache.
     * @return An iterator over the keys.
     */
    @Override
    public Iterator<String> iterator() {
        return cache.stringPropertyNames().iterator();
    }


    // -----  additional  -----


    /**
     * Override Object.toString().
     * @return information about this cache
     */
    @Override
    public String toString() {
        return String.format("<PropertiesfileCache:cachefile=%s;noOfEntries=%d>",
                cachefile, cache.size());
    }
}
