com.abelsky.idea.geekandpoke.entries.impl.OfflineCacheImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.abelsky.idea.geekandpoke.entries.impl.OfflineCacheImpl.java

Source

/*
 * 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 com.abelsky.idea.geekandpoke.entries.impl;

import com.abelsky.idea.geekandpoke.ComicsPlugin;
import com.abelsky.idea.geekandpoke.entries.Entry;
import com.abelsky.idea.geekandpoke.entries.EntryCache;
import com.abelsky.idea.geekandpoke.entries.EntryInfo;
import com.google.common.base.Function;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.ConcurrencyUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;
import java.util.concurrent.ExecutorService;

import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.Collections2.filter;
import static com.google.common.collect.Collections2.transform;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newLinkedList;
import static java.util.Collections.emptyList;

/**
 * Caches entries on disk in the IDEA plug-ins folder, like
 * <pre>%USERPROFILE%/.IdeaIC11/config/plugins/geek-and-poke/cache</pre>
 *
 * @author andy
 */
public class OfflineCacheImpl implements EntryCache {

    private final Logger log = Logger.getInstance(getClass());

    private final ExecutorService cachingLog = ConcurrencyUtil.newSingleThreadExecutor("Comics updater");

    @Nullable
    private final File root;

    public OfflineCacheImpl() {
        root = initRootDirectory();
        if (log.isDebugEnabled()) {
            log.debug("Using cache at \"" + root + "\"");
        }
    }

    private @Nullable File initRootDirectory() {
        final PluginId id = PluginId.getId(ComicsPlugin.PLUGIN_ID);

        @Nullable
        final IdeaPluginDescriptor plugin = PluginManager.getPlugin(id);
        log.assertTrue(plugin != null, "Cannot find plugin \"" + ComicsPlugin.PLUGIN_ID + "\"");

        //noinspection ConstantConditions
        final File pluginPath = plugin.getPath();

        if (pluginPath.isDirectory()) {
            // <my-plugin>/cache
            return new File(pluginPath, "cache");

        } else {
            // <plugins-directory>/<my-plugin>.jar
            final String absolutePath = pluginPath.getAbsolutePath();

            // <plugins-directory>
            final String rootPath = FilenameUtils.getFullPath(absolutePath);

            // <my-plugin>
            final String baseName = FilenameUtils.getBaseName(absolutePath);

            // <plugins-directory>/<my-plugin>.cache
            final File cachePath = new File(rootPath, baseName + ".cache");

            if (cachePath.isFile()) {
                FileUtil.delete(cachePath);
            }

            if (!cachePath.exists()) {
                if (!cachePath.mkdir()) {
                    // Couldn't create anything useful, just return some temporary directory.
                    if (log.isDebugEnabled()) {
                        log.debug("Couldn't initialize cache at " + cachePath);
                    }

                    try {
                        return FileUtil.createTempDirectory(baseName, "");
                    } catch (IOException e) {
                        if (log.isDebugEnabled()) {
                            log.debug("Couldn't create temporary directory with base name \"" + baseName
                                    + "\" in \"" + FileUtil.getTempDirectory() + "\"");
                        }
                    }
                }
            }

            return cachePath;
        }
    }

    private boolean isEnabled() {
        return root != null && root.isDirectory();
    }

    private List<File> getCacheContents() {
        if (!isEnabled()) {
            return emptyList();
        }

        @Nullable
        final File[] files = root.listFiles();
        if (files == null) {
            // Cache base is not a directory or IO error occurred.
            if (log.isDebugEnabled()) {
                log.debug("Cannot list files in: " + root);
            }

            return emptyList();
        }

        return newArrayList(files);
    }

    /**
     * @return All cached entries.
     */
    @Override
    public @NotNull List<Entry> getCached() {
        return newLinkedList(filter(transform(getCacheContents(), new Function<File, Entry>() {
            @Nullable
            @Override
            public Entry apply(File input) {
                return readFromFile(input);
            }
        }), notNull()));
    }

    /**
     * Tries to read persisted entry from the {@code file}.
     *
     * @return De-serialized object or {@code null} on any error.
     */
    private @Nullable Entry readFromFile(@NotNull File file) {
        if (log.isDebugEnabled()) {
            log.debug("Reading entry from \"" + file + "\"");
        }

        try {
            @NotNull
            final ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
            try {
                return (Entry) in.readObject();
            } finally {
                in.close();
            }
        } catch (ClassNotFoundException e) {
            log.error(e);
        } catch (IOException e) {
            if (log.isDebugEnabled()) {
                log.debug(e);
            }
        }
        return null;
    }

    @Override
    public @Nullable Entry getCached(@NotNull EntryInfo entry) {
        //noinspection SynchronizationOnLocalVariableOrMethodParameter
        synchronized (entry) {
            return readFromFile(new File(root, entry.getId()));
        }
    }

    private void physicallyDelete(@NotNull Entry entry) {
        if (!entryFile(entry).delete()) {
            if (log.isDebugEnabled()) {
                log.debug("Cannot delete entry: " + entry);
            }
        }
    }

    private void cache(@NotNull Entry entry) {
        if (!isEnabled()) {
            return;
        }

        synchronized (entry.getEntryInfo()) {
            if (getCached(entry.getEntryInfo()) != null) {
                physicallyDelete(entry);
            }

            @NotNull
            final File entryFile = entryFile(entry);
            FileUtil.createIfDoesntExist(entryFile);

            try {
                @NotNull
                final ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(entryFile));
                try {
                    out.writeObject(entry);
                } finally {
                    out.close();
                }
            } catch (IOException e) {
                if (log.isDebugEnabled()) {
                    log.warn(e);
                }

                // Ensure there's no incomplete entry.
                physicallyDelete(entry);
            }
        }
    }

    @Override
    public void asyncCache(@NotNull final Entry entry) {
        cachingLog.execute(new Runnable() {
            @Override
            public void run() {
                cache(entry);
            }
        });
    }

    @Override
    public void clear() {
        for (@NotNull
        File cacheElement : getCacheContents()) {
            FileUtil.delete(cacheElement);
        }
    }

    @Override
    public long getCacheSizeInBytes() {
        if (!isEnabled()) {
            return 0L;
        }
        return FileUtils.sizeOf(root);
    }

    private @NotNull File entryFile(@NotNull Entry e) {
        return new File(root, e.getEntryInfo().getId());
    }
}