All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.javac.PersistentUnitCacheDir Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * Copyright 2014 Google Inc.
 *
 * 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 com.google.gwt.dev.javac;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.jjs.impl.GwtAstBuilder;
import com.google.gwt.dev.util.CompilerVersion;
import com.google.gwt.dev.util.StringInterningObjectInputStream;
import com.google.gwt.dev.util.log.speedtracer.DevModeEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.util.tools.Utility;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.EOFException;
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.Collections;
import java.util.List;

/**
 * The directory containing persistent unit cache files.
 * (Helper class for {@link PersistentUnitCache}.)
 */
class PersistentUnitCacheDir {

  private static final String DIRECTORY_NAME = "gwt-unitCache";
  private static final String CACHE_FILE_PREFIX = "gwt-unitCache-";

  static final String CURRENT_VERSION_CACHE_FILE_PREFIX =
      CACHE_FILE_PREFIX + CompilerVersion.getHash() + "-";

  private final TreeLogger logger;
  private final File dir;

  // Non-null when a a cache file is open for writing. (Always true in normal operation.)
  private OpenFile openFile;

  /**
   * Finds the child directory where the cache files will be stored and opens a new cache
   * file for appending.
   */
  PersistentUnitCacheDir(TreeLogger logger, File parentDir) throws UnableToCompleteException {
    this.logger = logger;

    /*
     * We must canonicalize the path here, otherwise we might set cacheDirectory
     * to something like "/path/to/x/../gwt-unitCache". If this were to happen,
     * the mkdirs() call below would create "/path/to/gwt-unitCache" but
     * not "/path/to/x".
     * Further accesses via the uncanonicalized path will fail if "/path/to/x"
     * had not been created by other means.
     *
     * Fixes issue 6443
     */
    try {
      parentDir = parentDir.getCanonicalFile();
    } catch (IOException e) {
      logger.log(TreeLogger.WARN, "Can't get canonical directory for "
          + parentDir.getAbsolutePath(), e);
      throw new UnableToCompleteException();
    }

    dir = chooseCacheDir(parentDir);
    if (!dir.isDirectory() && !dir.mkdirs()) {
      logger.log(TreeLogger.WARN, "Can't create directory: " + dir.getAbsolutePath());
      throw new UnableToCompleteException();
    }

    if (!dir.canRead()) {
      logger.log(Type.WARN, "Can't read directory: " + dir.getAbsolutePath());
      throw new UnableToCompleteException();
    }

    logger.log(TreeLogger.TRACE, "Persistent unit cache dir set to: " + dir.getAbsolutePath());

    openFile = new OpenFile(logger, createEmptyCacheFile(logger, dir));
  }

  /**
   * Returns the absolute path of the directory where cache files are stored.
   */
  String getPath() {
    return dir.getAbsolutePath();
  }

  /**
   * Returns the number of files written to the cache directory and closed.
   */
  synchronized int getClosedCacheFileCount() {
    return selectClosedFiles(listFiles(CURRENT_VERSION_CACHE_FILE_PREFIX)).size();
  }

  /**
   * Load everything cached on disk into memory.
   */
  synchronized void loadUnitMap(PersistentUnitCache destination) {
    Event loadPersistentUnitEvent =
        SpeedTracerLogger.start(DevModeEventType.LOAD_PERSISTENT_UNIT_CACHE);
    if (logger.isLoggable(TreeLogger.TRACE)) {
      logger.log(TreeLogger.TRACE, "Looking for previously cached Compilation Units in "
          + getPath());
    }
    try {
      List files = selectClosedFiles(listFiles(CURRENT_VERSION_CACHE_FILE_PREFIX));
      for (File cacheFile : files) {
        loadOrDeleteCacheFile(cacheFile, destination);
      }
    } finally {
      loadPersistentUnitEvent.end();
    }
  }

  /**
   * Delete all cache files in the directory except for the currently open file.
   */
  synchronized void deleteClosedCacheFiles() {
    SpeedTracerLogger.Event deleteEvent = SpeedTracerLogger.start(DevModeEventType.DELETE_CACHE);
    logger.log(TreeLogger.TRACE, "Deleting cache files from " + dir);

    // We want to delete cache files from previous versions as well.
    List allVersionsList = listFiles(CACHE_FILE_PREFIX);
    int deleteCount = 0;
    for (File candidate : allVersionsList) {
      if (deleteUnlessOpen(candidate)) {
        deleteCount++;
      }
    }

    logger.log(TreeLogger.TRACE, "Deleted " + deleteCount + " cache files from " + dir);
    deleteEvent.end();
  }

  /**
   * Closes the current cache file and opens a new one.
   */
  synchronized void rotate() throws UnableToCompleteException {
    logger.log(Type.TRACE, "Rotating persistent unit cache");
    if (openFile != null) {
      openFile.close(logger);
      openFile = null;
    }
    openFile = new OpenFile(logger, createEmptyCacheFile(logger, dir));
  }

  /**
   * Deletes the given file unless it's currently open for writing.
   */
  synchronized boolean deleteUnlessOpen(File cacheFile) {
    if (isOpen(cacheFile)) {
      return false;
    }
    logger.log(Type.TRACE, "Deleting file: " + cacheFile);
    boolean deleted = cacheFile.delete();
    if (!deleted) {
      logger.log(Type.WARN, "Unable to delete file: " + cacheFile);
    }
    return deleted;
  }

  /**
   * Writes a compilation unit to the disk cache.
   */
  synchronized void writeUnit(CompilationUnit unit) throws UnableToCompleteException {
    if (openFile == null) {
      logger.log(Type.TRACE, "Skipped writing compilation unit to cache because no file is open");
      return;
    }
    openFile.writeUnit(logger, unit);
  }

  /**
   * Closes the file where cache entries are written.
   * (This should only be called at shutdown.)
   */
  synchronized void closeCurrentFile() {
    if (openFile != null) {
      openFile.close(logger);
      openFile = null;
    }
  }

  @VisibleForTesting
  static File chooseCacheDir(File parentDir) {
    return new File(parentDir, DIRECTORY_NAME);
  }

  private boolean isOpen(File f) {
    return openFile != null && openFile.file.equals(f);
  }

  /**
   * Loads all the units in a cache file into the given cache.
   * Delete it if unable to read it.
   */
  private void loadOrDeleteCacheFile(File cacheFile, PersistentUnitCache destination) {
    FileInputStream fis = null;
    BufferedInputStream bis = null;
    ObjectInputStream inputStream = null;

    boolean ok = false;
    int unitsLoaded = 0;
    try {
      fis = new FileInputStream(cacheFile);
      bis = new BufferedInputStream(fis);
      /*
       * It is possible for the next call to throw an exception, leaving
       * inputStream null and fis still live.
       */
      inputStream = new StringInterningObjectInputStream(bis);

      // Read objects until we get an EOF exception.
      while (true) {
        CachedCompilationUnit unit = (CachedCompilationUnit) inputStream.readObject();
        if (unit == null) {
          // Won't normally get here. Not sure why this check was here before.
          logger.log(Type.WARN, "unexpected null in cache file: " + cacheFile);
          break;
        }
        if (unit.getTypesSerializedVersion() != GwtAstBuilder.getSerializationVersion()) {
          continue;
        }
        destination.maybeAddLoadedUnit(unit);
        unitsLoaded++;
      }

    } catch (EOFException ignored) {
      // This is a normal exit. Go on to the next file.
      ok = true;
    } catch (IOException e) {
      logger.log(TreeLogger.TRACE, "Ignoring and deleting cache log "
          + cacheFile.getAbsolutePath() + " due to read error.", e);
    } catch (ClassNotFoundException e) {
      logger.log(TreeLogger.TRACE, "Ignoring and deleting cache log "
          + cacheFile.getAbsolutePath() + " due to deserialization error.", e);
    } finally {
      Utility.close(inputStream);
      Utility.close(bis);
      Utility.close(fis);
    }

    if (ok) {
      logger.log(TreeLogger.TRACE, "Loaded " + unitsLoaded +
          " units from cache file: " + cacheFile.getName());
    } else {
      deleteUnlessOpen(cacheFile);
      logger.log(TreeLogger.TRACE, "Loaded " + unitsLoaded +
          " units from invalid cache file before deleting it: " + cacheFile.getName());
    }
  }

  /**
   * Lists files in the cache directory that start with the given prefix.
   *
   * 

The files will be sorted according to {@link java.io.File#compareTo}, which * differs on Unix versus Windows, but is good enough to sort by age * for the names we use.

*/ private List listFiles(String prefix) { File[] files = dir.listFiles(); if (files == null) { // Shouldn't happen, just satisfying null check warning. return Collections.emptyList(); } List out = Lists.newArrayList(); for (File file : files) { if (file.getName().startsWith(prefix)) { out.add(file); } } Collections.sort(out); return out; } /** * Removes the currently open file from a list of files. * @return the new list. */ private List selectClosedFiles(Iterable fileList) { List closedFiles = Lists.newArrayList(); for (File file : fileList) { if (!isOpen(file)) { closedFiles.add(file); } } return closedFiles; } /** * Creates a new, empty file with a name based on the current system time. */ private static File createEmptyCacheFile(TreeLogger logger, File dir) throws UnableToCompleteException { File newFile = null; long timestamp = System.currentTimeMillis(); try { do { newFile = new File(dir, CURRENT_VERSION_CACHE_FILE_PREFIX + String.format("%016X", timestamp++)); } while (!newFile.createNewFile()); } catch (IOException ex) { logger.log(TreeLogger.WARN, "Can't create new cache log file " + newFile.getAbsolutePath() + ".", ex); throw new UnableToCompleteException(); } if (!newFile.canWrite()) { logger.log(TreeLogger.WARN, "Can't write to new cache log file " + newFile.getAbsolutePath() + "."); throw new UnableToCompleteException(); } return newFile; } /** * The current file and stream being written to by the persistent unit cache, if any. * *

Not thread safe. (The parent class handles concurrency.) */ private static class OpenFile { private final File file; private final ObjectOutputStream stream; private int unitsWritten = 0; /** * Opens a file for writing compilation units. * Overwrites the file (it's typically empty). * A cache file may not already be open. */ OpenFile(TreeLogger logger, File toOpen) throws UnableToCompleteException { logger.log(Type.TRACE, "Opening cache file: " + toOpen); ObjectOutputStream newStream = openObjectStream(logger, toOpen); this.file = toOpen; this.stream = newStream; unitsWritten = 0; } /** * Writes a compilation unit to the currently open file, if any. * @return true if written * @throws UnableToCompleteException if the file was open but we can't append. */ boolean writeUnit(TreeLogger logger, CompilationUnit unit) throws UnableToCompleteException { try { stream.writeObject(unit); unitsWritten++; return true; } catch (IOException e) { logger.log(TreeLogger.ERROR, "Error saving compilation unit to cache file: " + file, e); throw new UnableToCompleteException(); } } /** * Closes the current file and deletes it if it's empty. If no file is open, does nothing. */ void close(TreeLogger logger) { logger.log(Type.TRACE, "Closing cache file: " + file + " (" + unitsWritten + " units written)"); Utility.close(stream); if (unitsWritten == 0) { // Remove useless empty file. logger.log(Type.TRACE, "Deleting empty file: " + file); boolean deleted = file.delete(); if (!deleted) { logger.log(Type.INFO, "Couldn't delete persistent unit cache file: " + file); } } } private static ObjectOutputStream openObjectStream(TreeLogger logger, File file) throws UnableToCompleteException { FileOutputStream fstream = null; try { fstream = new FileOutputStream(file); return new ObjectOutputStream(new BufferedOutputStream(fstream)); } catch (IOException e) { logger.log(Type.ERROR, "Can't open persistent unit cache file", e); Utility.close(fstream); throw new UnableToCompleteException(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy