org.splevo.jamopp.extraction.cache.ReferenceCache.java Source code

Java tutorial

Introduction

Here is the source code for org.splevo.jamopp.extraction.cache.ReferenceCache.java

Source

/*******************************************************************************
 * Copyright (c) 2014
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Benjamin Klatt - initial API and implementation and/or initial documentation
 *******************************************************************************/
package org.splevo.jamopp.extraction.cache;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.emftext.language.java.JavaClasspath;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * A file based cache to reuse the proxy resolutions already performed.
 * 
 * The cache was designed to work with one or more cache files to use it with resource sets
 * containing the resources of one or more software models. For example, during extraction a
 * separate resource set is used per software, but for differencing several software models must be
 * accessed in one resource set.
 * 
 * Cache files are always named according to {@link #CACHE_FILE_NAME}.
 * 
 * During initialization, cache files existing in the provided directories are loaded.
 * Subdirectories are not considered.
 * 
 * When proxies in new resources are resolved and {@link #save()} is triggered, they are stored in a
 * cache file of the first directory provided in the list. If a cache file already exists in the
 * first cache directory, the existing cache is loaded and enhanced with the new cached references.
 */
public class ReferenceCache {

    /** The name of the cache files to be used. */
    public static final String CACHE_FILE_NAME = "jamopp.cache";

    private static Logger logger = Logger.getLogger(ReferenceCache.class);

    /**
     * Internal counter how many references have not been resolved from cache, while their resources
     * have. This is an indicator for failed proxy resolutions or that the cache was not involved in
     * the resolution.
     * 
     */
    private int notResolvedFromCacheCounterReference = 0;

    private final Set<URI> blacklistedResourceURIs = Sets.newHashSet();

    /**
     * The file the cache will be serialized into.
     */
    private final List<String> cacheFileDirectories;

    /** The cache data object to work with. */
    private ReferenceCacheData cacheData = new ReferenceCacheData();

    /**
     * Constructor to set a list of directories containing cache files. Within these directories,
     * files with the name {@link #CACHE_FILE_NAME} are searched.
     * 
     * If a new file must be created, this will be done in the first directory of the list.
     * 
     * @param cacheFileDirectories
     *            A list of absolute paths to the directories containing cache files.
     */
    public ReferenceCache(List<String> cacheFileDirectories) {
        this.cacheFileDirectories = cacheFileDirectories;
        init();
    }

    /**
     * Initialize the cache by loading all cache files available in the configured directory.
     * 
     * In addition, register the jar files in the {@link JavaClasspath}.
     */
    private void init() {
        for (String cacheDirectory : this.cacheFileDirectories) {
            File cacheFile = new File(cacheDirectory + File.separator + CACHE_FILE_NAME);
            if (cacheFile.exists() && cacheFile.canRead()) {
                ReferenceCacheData loadedCacheData = load(cacheFile);
                if (loadedCacheData != null) {
                    cacheData.merge(loadedCacheData);
                }
            }
        }
    }

    /**
     * Forces the complete resolving of the resource.
     * 
     * <p>
     * <strong>Note:</strong> This should be used for loading the cache only. It is also recommended
     * to call this method not before all resources are present in the ResourceSet.
     * </p>
     * 
     * @param resource
     *            Resource to be resolved.
     */
    public void resolve(Resource resource) {
        EcoreUtil.resolveAll(resource);
    }

    /**
     * Trigger to save all non yet persisted cache entries.<br>
     * These are the entries created for resources that could not be loaded from any existing cache
     * file.
     * 
     * If more than one cache file directory was created, the first entry in the list will be used.
     * 
     * If the cache file already exists, it will not be overridden, but loaded and the new entries
     * will be added to it.
     * 
     */
    public void save() {

        if (cacheFileDirectories == null || cacheFileDirectories.size() < 1
                || cacheFileDirectories.get(0) == null) {
            logger.warn("No cache file directory(ies) configured");
            return;
        }

        File cacheFile = new File(cacheFileDirectories.get(0) + File.separator + CACHE_FILE_NAME);
        ReferenceCacheData cacheDataExisting = load(cacheFile);
        if (cacheDataExisting == null) {
            cacheDataExisting = new ReferenceCacheData();
            cacheData.merge(cacheDataExisting);
        }

        save(cacheFile, cacheData);
    }

    /**
     * Persist the cache in the file system.
     * 
     * @param cacheFile
     *            The file to save to.
     * @param cacheData
     *            The cache data to save.
     */
    public synchronized void save(File cacheFile, ReferenceCacheData cacheData) {
        ObjectOutputStream oos = null;
        try {
            FileUtils.forceMkdir(cacheFile.getParentFile());
            oos = new ObjectOutputStream(new FileOutputStream(cacheFile));
            oos.writeObject(cacheData);
        } catch (FileNotFoundException e) {
            logger.info("cache file does not exist yet" + cacheFile);
        } catch (IOException e) {
            logger.warn("cache file could not be accessed: " + cacheFile);
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    logger.warn("Failed to close cache file output stream", e);
                }
            }
        }
    }

    /**
     * Load the cache from a file.
     * 
     * @param cacheFile
     *            The file to load.
     * @return The cache map loaded from this file.
     */
    private ReferenceCacheData load(File cacheFile) {

        if (!cacheFile.exists() && !cacheFile.canRead()) {
            return null;
        }

        logger.debug("Load reference cache file: " + cacheFile.getAbsolutePath());

        ReferenceCacheData cacheDataLoad = null;
        ObjectInputStream oos = null;
        try {
            oos = new ObjectInputStream(new FileInputStream(cacheFile));
            cacheDataLoad = (ReferenceCacheData) oos.readObject();
        } catch (FileNotFoundException e) {
            logger.error("Cache file can not be found", e);
        } catch (IOException e) {
            logger.error("Cache file could not be accessed correctly", e);
        } catch (ClassNotFoundException e) {
            logger.error("An object persisted in the cache file could not be loaded", e);
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    logger.warn("Failed to close cache file output stream", e);
                }
            }
        }

        return cacheDataLoad;
    }

    /**
     * Get the target object for a specified URI.
     * 
     * @param resource
     *            The resource to use for EObject resolution.
     * @param targetURI
     *            The URI to resolve the target object with.
     * @return The resolved EObject.
     */
    private EObject getTarget(Resource resource, String targetURI) {
        URI createURI = URI.createURI(targetURI);
        ResourceSet resourceSet = resource.getResourceSet();
        EObject target = resourceSet.getEObject(createURI, true);
        return target;
    }

    /**
     * Get the internal counter how many references have not been resolved from cache, while their
     * resources have. This is an indicator for failed proxy resolutions or that the cache was not
     * involved in the resolution.
     * 
     * @return The counter value.
     */
    public int getNotResolvedFromCacheCounterReference() {
        return notResolvedFromCacheCounterReference;
    }

    /**
     * Resolve a specific proxy referenced in resource.
     * 
     * @param resource
     *            The resource containing the proxy.
     * @param id
     *            The id (local fragment uri) of the proxy.
     * @return The object resolved for the id.
     */
    public EObject getEObject(Resource resource, String id) {

        String resourceUri = resource.getURI().toString();
        LinkedHashMap<String, String> targetUriMap = cacheData.getResourceToTargetURIListMap().get(resourceUri);
        if (targetUriMap == null) {
            return null;
        }
        String targetURI = targetUriMap.get(id);
        if (targetURI == null) {
            return null;
        }

        return getTarget(resource, targetURI);
    }

    /**
     * Check is already present in the cached.
     * 
     * @param resource
     *            The resource to check for.
     * @return True/ False if it is cached or not.
     */
    public boolean isCached(Resource resource) {
        return cacheData.getResourceToTargetURIListMap().containsKey(resource.getURI().toString());
    }

    /**
     * Register a resolved {@link EObject} to the cache which has been resolved by EMF without
     * involving the cache. E.g. by indirectly resolving it without participating the cache.
     * 
     * @param resource
     *            The resource containing the proxy / reference
     * @param fragmentURI
     *            The fragmentURI if the proxy
     * @param resolvedElement
     *            The resolved element
     */
    public void registerEObject(Resource resource, String fragmentURI, EObject resolvedElement) {

        if (resolvedElement != null && resolvedElement.eResource() != null
                && resolvedElement.eResource().getURI() != null
                && blacklistedResourceURIs.contains(resolvedElement.eResource().getURI())) {
            return;
        }

        String resourceUri = resource.getURI().toString();

        if (resolvedElement == null) {
            logger.warn(
                    String.format("Tried to register a null element in the cache %s#%s", resourceUri, fragmentURI));
            return;
        }

        if (resolvedElement.eIsProxy()) {
            if (isNotLibraryProxy(resolvedElement)) {
                logger.warn(String.format("Tried to register a non-library proxy in the cache: %s#%s", resourceUri,
                        fragmentURI));
                return;
            }
        }

        String targetURI;
        Resource targetResource = resolvedElement.eResource();
        if (targetResource != null) {
            targetURI = targetResource.getURI().toString() + "#" + targetResource.getURIFragment(resolvedElement);
        } else if (resolvedElement.eIsProxy()) {
            targetURI = ((InternalEObject) resolvedElement).eProxyURI().toString();
        } else {
            logger.error("Unable to identify target URI of resolved element: " + resolvedElement);
            return;
        }

        LinkedHashMap<String, String> targetURIMap = cacheData.getResourceToTargetURIListMap().get(resourceUri);
        if (targetURIMap == null) {
            targetURIMap = Maps.newLinkedHashMap();
            cacheData.getResourceToTargetURIListMap().put(resourceUri, targetURIMap);
        }

        targetURIMap.put(fragmentURI, targetURI);
        notResolvedFromCacheCounterReference++;
    }

    private boolean isNotLibraryProxy(EObject resolvedElement) {
        return !("pathmap".equals(((InternalEObject) resolvedElement).eProxyURI().scheme()));
    }

    /**
     * Resets the cache for the given resource and saves the cache afterwards to prevent old entries
     * from appearing after loading the resource again.
     * 
     * @param resource
     *            The resource for which the cache shall be reset.
     */
    public void reset(Resource resource) {
        if (!isCached(resource)) {
            return;
        }
        final String uriToRemovePrefix = resource.getURI().toString() + "#";
        cacheData.getResourceToTargetURIListMap().remove(resource.getURI().toString());
        for (LinkedHashMap<String, String> map : cacheData.getResourceToTargetURIListMap().values()) {
            List<String> toRemove = Lists.newArrayList();
            for (Entry<String, String> entry : map.entrySet()) {
                if (entry.getValue().startsWith(uriToRemovePrefix)) {
                    toRemove.add(entry.getKey());
                }
            }
            map.keySet().removeAll(toRemove);
        }
        save();
    }

    /**
     * Blacklists the given resource by its URI. The cache will ignore any attempts to set a cache
     * line involving the given resource.
     * 
     * @param resource
     *            The resource to be blacklisted.
     */
    public void blacklist(Resource resource) {
        reset(resource);
        blacklistedResourceURIs.add(resource.getURI());
    }
}