Example #1
0
/**
 * Stores all information for a given case. Only a single case can currently be open at a time. Use
 * getCurrentCase() to retrieve the object for the current case.
 */
public class Case {

  private static final String autopsyVer =
      Version.getVersion(); // current version of autopsy. Change it when the version is changed
  private static final String appName = Version.getName() + " " + autopsyVer;
  /**
   * Property name that indicates the name of the current case has changed. Fired with the case is
   * renamed, and when the current case is opened/closed/changed. The value is a String: the name of
   * the case. The empty string ("") is used for no open case.
   */
  public static final String CASE_NAME = "caseName";
  /**
   * Property name that indicates the number of the current case has changed. Fired with the case
   * number is changed. The value is an int: the number of the case. -1 is used for no case number
   * set.
   */
  public static final String CASE_NUMBER = "caseNumber";
  /**
   * Property name that indicates the examiner of the current case has changed. Fired with the case
   * examiner is changed. The value is a String: the name of the examiner. The empty string ("") is
   * used for no examiner set.
   */
  public static final String CASE_EXAMINER = "caseExaminer";
  /**
   * Property name that indicates a new data source (image, disk or local file) has been added to
   * the current case. The new value is the newly-added instance of the new data source, and the old
   * value is always null.
   */
  public static final String CASE_ADD_DATA_SOURCE = "addDataSource";
  /**
   * Property name that indicates a data source has been removed from the current case. The "old
   * value" is the (int) content ID of the data source that was removed, the new value is the
   * instance of the data source.
   */
  public static final String CASE_DEL_DATA_SOURCE = "removeDataSource";
  /**
   * Property name that indicates the currently open case has changed. The new value is the instance
   * of the opened Case, or null if there is no open case. The old value is the instance of the
   * closed Case, or null if there was no open case.
   */
  public static final String CASE_CURRENT_CASE = "currentCase";
  /** Name for the property that determines whether to show the dialog at startup */
  public static final String propStartup = "LBL_StartupDialog";
  // pcs is initialized in CaseListener constructor
  private static final PropertyChangeSupport pcs = new PropertyChangeSupport(Case.class);
  private String name;
  private String number;
  private String examiner;
  private String configFilePath;
  private XMLCaseManagement xmlcm;
  private SleuthkitCase db;
  // Track the current case (only set with changeCase() method)
  private static Case currentCase = null;
  private Services services;
  private static final Logger logger = Logger.getLogger(Case.class.getName());
  static final String CASE_EXTENSION = "aut";
  static final String CASE_DOT_EXTENSION = "." + CASE_EXTENSION;

  /** Constructor for the Case class */
  private Case(
      String name,
      String number,
      String examiner,
      String configFilePath,
      XMLCaseManagement xmlcm,
      SleuthkitCase db) {
    this.name = name;
    this.number = number;
    this.examiner = examiner;
    this.configFilePath = configFilePath;
    this.xmlcm = xmlcm;
    this.db = db;
    this.services = new Services(db);
  }

  /**
   * Gets the currently opened case, if there is one.
   *
   * @return the current open case
   * @throws IllegalStateException if there is no case open.
   */
  public static Case getCurrentCase() {
    if (currentCase != null) {
      return currentCase;
    } else {
      throw new IllegalStateException("Can't get the current case; there is no case open!");
    }
  }

  /**
   * Check if case is currently open
   *
   * @return true if case is open
   */
  public static boolean isCaseOpen() {
    return currentCase != null;
  }

  /**
   * Updates the current case to the given case and fires off the appropriate property-change
   *
   * @param newCase the new current case
   */
  private static void changeCase(Case newCase) {

    Case oldCase = Case.currentCase;
    Case.currentCase = null;

    String oldCaseName = oldCase != null ? oldCase.name : "";

    doCaseChange(null); // closes windows, etc
    pcs.firePropertyChange(CASE_CURRENT_CASE, oldCase, null);

    doCaseNameChange("");
    pcs.firePropertyChange(CASE_NAME, oldCaseName, "");

    if (newCase != null) {
      currentCase = newCase;

      pcs.firePropertyChange(CASE_CURRENT_CASE, null, currentCase);
      doCaseChange(currentCase);

      pcs.firePropertyChange(CASE_NAME, "", currentCase.name);
      doCaseNameChange(currentCase.name);

      RecentCases.getInstance()
          .addRecentCase(currentCase.name, currentCase.configFilePath); // update the recent cases
    }
  }

  AddImageProcess makeAddImageProcess(
      String timezone, boolean processUnallocSpace, boolean noFatOrphans) {
    return this.db.makeAddImageProcess(timezone, processUnallocSpace, noFatOrphans);
  }

  /**
   * Creates a new case (create the XML config file and database)
   *
   * @param caseDir The directory to store case data in. Will be created if it doesn't already
   *     exist. If it exists, it should have all of the needed sub dirs that createCaseDirectory()
   *     will create.
   * @param caseName the name of case
   * @param caseNumber the case number
   * @param examiner the examiner for this case
   */
  public static void create(String caseDir, String caseName, String caseNumber, String examiner)
      throws CaseActionException {
    logger.log(
        Level.INFO,
        "Creating new case.\ncaseDir: {0}\ncaseName: {1}",
        new Object[] {caseDir, caseName});

    // create case directory if it doesn't already exist.
    if (new File(caseDir).exists() == false) {
      Case.createCaseDirectory(caseDir);
    }

    String configFilePath = caseDir + File.separator + caseName + CASE_DOT_EXTENSION;

    XMLCaseManagement xmlcm = new XMLCaseManagement();
    xmlcm.create(caseDir, caseName, examiner, caseNumber); // create a new XML config file
    xmlcm.writeFile();

    String dbPath = caseDir + File.separator + "autopsy.db";
    SleuthkitCase db = null;
    try {
      db = SleuthkitCase.newCase(dbPath);
    } catch (TskCoreException ex) {
      logger.log(Level.SEVERE, "Error creating a case: " + caseName + " in dir " + caseDir, ex);
      throw new CaseActionException(
          "Error creating a case: " + caseName + " in dir " + caseDir, ex);
    }

    Case newCase = new Case(caseName, caseNumber, examiner, configFilePath, xmlcm, db);

    changeCase(newCase);
  }

  /**
   * Opens the existing case (open the XML config file)
   *
   * @param configFilePath the path of the configuration file that's opened
   * @throws CaseActionException
   */
  static void open(String configFilePath) throws CaseActionException {
    logger.log(Level.INFO, "Opening case.\nconfigFilePath: {0}", configFilePath);

    try {
      XMLCaseManagement xmlcm = new XMLCaseManagement();

      xmlcm.open(
          configFilePath); // open and load the config file to the document handler in the XML class
      xmlcm.writeFile(); // write any changes to the config file

      String caseName = xmlcm.getCaseName();
      String caseNumber = xmlcm.getCaseNumber();
      String examiner = xmlcm.getCaseExaminer();
      // if the caseName is "", case / config file can't be opened
      if (caseName.equals("")) {
        throw new CaseActionException("Case name is blank.");
      }

      String caseDir = xmlcm.getCaseDirectory();
      String dbPath = caseDir + File.separator + "autopsy.db";
      SleuthkitCase db = SleuthkitCase.openCase(dbPath);

      checkImagesExist(db);

      Case openedCase = new Case(caseName, caseNumber, examiner, configFilePath, xmlcm, db);

      changeCase(openedCase);

    } catch (Exception ex) {
      logger.log(Level.SEVERE, "Error opening the case: ", ex);
      // close the previous case if there's any
      CaseCloseAction closeCase = SystemAction.get(CaseCloseAction.class);
      closeCase.actionPerformed(null);
      if (!configFilePath.endsWith(CASE_DOT_EXTENSION)) {
        throw new CaseActionException(
            "Check that you selected the correct case file (usually with "
                + CASE_DOT_EXTENSION
                + " extension)",
            ex);
      } else {
        throw new CaseActionException("Error opening the case", ex);
      }
    }
  }

  static Map<Long, String> getImagePaths(SleuthkitCase db) { // TODO: clean this up
    Map<Long, String> imgPaths = new HashMap<Long, String>();
    try {
      Map<Long, List<String>> imgPathsList = db.getImagePaths();
      for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
        if (entry.getValue().size() > 0) {
          imgPaths.put(entry.getKey(), entry.getValue().get(0));
        }
      }
    } catch (TskException ex) {
      logger.log(Level.WARNING, "Error getting image paths", ex);
    }
    return imgPaths;
  }

  /** Ensure that all image paths point to valid image files */
  private static void checkImagesExist(SleuthkitCase db) {
    Map<Long, String> imgPaths = getImagePaths(db);
    for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
      long obj_id = entry.getKey();
      String path = entry.getValue();
      boolean fileExists = (pathExists(path) || driveExists(path));
      if (!fileExists) {
        int ret =
            JOptionPane.showConfirmDialog(
                null,
                appName
                    + " has detected that one of the images associated with \n"
                    + "this case are missing. Would you like to search for them now?\n"
                    + "Previously, the image was located at:\n"
                    + path
                    + "\nPlease note that you will still be able to browse directories and generate reports\n"
                    + "if you choose No, but you will not be able to view file content or run the ingest process.",
                "Missing Image",
                JOptionPane.YES_NO_OPTION);
        if (ret == JOptionPane.YES_OPTION) {
          MissingImageDialog.makeDialog(obj_id, db);
        } else {
          logger.log(Level.WARNING, "Selected image files don't match old files!");
        }
      }
    }
  }

  /**
   * Adds the image to the current case after it has been added to the DB Sends out event and
   * reopens windows if needed.
   *
   * @param imgPaths the paths of the image that being added
   * @param imgId the ID of the image that being added
   * @param timeZone the timeZone of the image where it's added
   */
  Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
    logger.log(
        Level.INFO,
        "Adding image to Case.  imgPath: {0}  ID: {1} TimeZone: {2}",
        new Object[] {imgPath, imgId, timeZone});

    try {
      Image newImage = db.getImageById(imgId);
      pcs.firePropertyChange(
          CASE_ADD_DATA_SOURCE, null, newImage); // the new value is the instance of the image
      CoreComponentControl.openCoreWindows();
      return newImage;
    } catch (Exception ex) {
      throw new CaseActionException("Error adding image to the case", ex);
    }
  }

  /**
   * Finishes adding new local data source to the case Sends out event and reopens windows if
   * needed.
   *
   * @param newDataSource new data source added
   */
  void addLocalDataSource(Content newDataSource) {
    pcs.firePropertyChange(CASE_ADD_DATA_SOURCE, null, newDataSource);
    CoreComponentControl.openCoreWindows();
  }

  /** @return The Services object for this case. */
  public Services getServices() {
    return services;
  }

  /**
   * Get the underlying SleuthkitCase instance from the Sleuth Kit bindings library.
   *
   * @return
   */
  public SleuthkitCase getSleuthkitCase() {
    return this.db;
  }

  /** Closes this case. This methods close the xml and clear all the fields. */
  void closeCase() throws CaseActionException {
    changeCase(null);

    try {
      services.close();
      this.xmlcm.close(); // close the xmlcm
      this.db.close();
    } catch (Exception e) {
      throw new CaseActionException("Error while trying to close the current case.", e);
    }
  }

  /**
   * Delete this case. This methods delete all folders and files of this case.
   *
   * @param caseDir case dir to delete
   * @throws CaseActionException exception throw if case could not be deleted
   */
  void deleteCase(File caseDir) throws CaseActionException {
    logger.log(Level.INFO, "Deleting case.\ncaseDir: {0}", caseDir);

    try {

      xmlcm.close(); // close the xmlcm
      boolean result = deleteCaseDirectory(caseDir); // delete the directory

      RecentCases.getInstance()
          .removeRecentCase(this.name, this.configFilePath); // remove it from the recent case
      Case.changeCase(null);
      if (result == false) {
        throw new CaseActionException("Error deleting the case dir: " + caseDir);
      }
    } catch (Exception ex) {
      logger.log(Level.SEVERE, "Error deleting the current case dir: " + caseDir, ex);
      throw new CaseActionException("Error deleting the case dir: " + caseDir, ex);
    }
  }

  /**
   * Updates the case name.
   *
   * @param oldCaseName the old case name that wants to be updated
   * @param oldPath the old path that wants to be updated
   * @param newCaseName the new case name
   * @param newPath the new path
   */
  void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath)
      throws CaseActionException {
    try {
      xmlcm.setCaseName(newCaseName); // set the case
      name = newCaseName; // change the local value
      RecentCases.getInstance()
          .updateRecentCase(oldCaseName, oldPath, newCaseName, newPath); // update the recent case

      pcs.firePropertyChange(CASE_NAME, oldCaseName, newCaseName);
      doCaseNameChange(newCaseName);

    } catch (Exception e) {
      throw new CaseActionException("Error while trying to update the case name.", e);
    }
  }

  /**
   * Updates the case examiner
   *
   * @param oldExaminer the old examiner
   * @param newExaminer the new examiner
   */
  void updateExaminer(String oldExaminer, String newExaminer) throws CaseActionException {
    try {
      xmlcm.setCaseExaminer(newExaminer); // set the examiner
      examiner = newExaminer;

      pcs.firePropertyChange(CASE_EXAMINER, oldExaminer, newExaminer);
    } catch (Exception e) {
      throw new CaseActionException("Error while trying to update the examiner.", e);
    }
  }

  /**
   * Updates the case number
   *
   * @param oldCaseNumber the old case number
   * @param newCaseNumber the new case number
   */
  void updateCaseNumber(String oldCaseNumber, String newCaseNumber) throws CaseActionException {
    try {
      xmlcm.setCaseNumber(newCaseNumber); // set the case number
      number = newCaseNumber;

      pcs.firePropertyChange(CASE_NUMBER, oldCaseNumber, newCaseNumber);
    } catch (Exception e) {
      throw new CaseActionException("Error while trying to update the case number.", e);
    }
  }

  /**
   * Checks whether there is a current case open.
   *
   * @return True if a case is open.
   */
  public static boolean existsCurrentCase() {
    return currentCase != null;
  }

  /**
   * Uses the given path to store it as the configuration file path
   *
   * @param givenPath the given config file path
   */
  private void setConfigFilePath(String givenPath) {
    configFilePath = givenPath;
  }

  /**
   * Get the config file path in the given path
   *
   * @return configFilePath the path of the configuration file
   */
  String getConfigFilePath() {
    return configFilePath;
  }

  /**
   * Returns the current version of Autopsy
   *
   * @return autopsyVer
   */
  public static String getAutopsyVersion() {
    return autopsyVer;
  }

  /**
   * Gets the application name
   *
   * @return appName
   */
  public static String getAppName() {
    return appName;
  }

  /**
   * Gets the case name
   *
   * @return name
   */
  public String getName() {
    return name;
  }

  /**
   * Gets the case number
   *
   * @return number
   */
  public String getNumber() {
    return number;
  }

  /**
   * Gets the Examiner name
   *
   * @return examiner
   */
  public String getExaminer() {
    return examiner;
  }

  /**
   * Gets the case directory path
   *
   * @return caseDirectoryPath
   */
  public String getCaseDirectory() {
    if (xmlcm == null) {
      return "";
    } else {
      return xmlcm.getCaseDirectory();
    }
  }

  /**
   * Gets the full path to the temp directory of this case
   *
   * @return tempDirectoryPath
   */
  public String getTempDirectory() {
    if (xmlcm == null) {
      return "";
    } else {
      return xmlcm.getTempDir();
    }
  }

  /**
   * Gets the full path to the cache directory of this case
   *
   * @return cacheDirectoryPath
   */
  public String getCacheDirectory() {
    if (xmlcm == null) {
      return "";
    } else {
      return xmlcm.getCacheDir();
    }
  }

  /**
   * get the created date of this case
   *
   * @return case creation date
   */
  public String getCreatedDate() {
    if (xmlcm == null) {
      return "";
    } else {
      return xmlcm.getCreatedDate();
    }
  }

  /**
   * Get absolute module output directory path where modules should save their permanent data The
   * directory is a subdirectory of this case dir.
   *
   * @return absolute path to the module output dir
   */
  public String getModulesOutputDirAbsPath() {
    return this.getCaseDirectory() + File.separator + getModulesOutputDirRelPath();
  }

  /**
   * Get relative (with respect to case dir) module output directory path where modules should save
   * their permanent data The directory is a subdirectory of this case dir.
   *
   * @return relative path to the module output dir
   */
  public static String getModulesOutputDirRelPath() {
    return "ModuleOutput";
  }

  /**
   * get the PropertyChangeSupport of this class
   *
   * @return PropertyChangeSupport
   */
  public static PropertyChangeSupport getPropertyChangeSupport() {
    return pcs;
  }

  String getImagePaths(Long imgID) {
    return getImagePaths(db).get(imgID);
  }

  /**
   * get all the image id in this case
   *
   * @return imageIDs
   */
  public Long[] getImageIDs() {
    Set<Long> ids = getImagePaths(db).keySet();
    return ids.toArray(new Long[ids.size()]);
  }

  public List<Image> getImages() throws TskCoreException {
    return db.getImages();
  }

  /**
   * Count the root objects.
   *
   * @return The number of total root objects in this case.
   */
  public int getRootObjectsCount() {
    return getRootObjects().size();
  }

  /**
   * Get the data model Content objects in the root of this case's hierarchy.
   *
   * @return a list of the root objects
   */
  public List<Content> getRootObjects() {
    try {
      return db.getRootObjects();
    } catch (TskException ex) {
      throw new RuntimeException("Error getting root objects.", ex);
    }
  }

  /**
   * Gets the time zone(s) of the image(s) in this case.
   *
   * @return time zones the set of time zones
   */
  public Set<TimeZone> getTimeZone() {
    Set<TimeZone> timezones = new HashSet<TimeZone>();
    for (Content c : getRootObjects()) {
      try {
        final Image image = c.getImage();
        if (image != null) {
          timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
        }
      } catch (TskException ex) {
        logger.log(Level.INFO, "Error getting time zones", ex);
      }
    }
    return timezones;
  }

  public static synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
    pcs.addPropertyChangeListener(listener);
  }

  public static synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
    pcs.removePropertyChangeListener(listener);
  }

  /**
   * Check if image from the given image path exists.
   *
   * @param imgPath the image path
   * @return isExist whether the path exists
   */
  public static boolean pathExists(String imgPath) {
    return new File(imgPath).isFile();
  }
  /** Does the given string refer to a physical drive? */
  private static final String pdisk = "\\\\.\\physicaldrive";

  private static final String dev = "/dev/";

  static boolean isPhysicalDrive(String path) {
    return path.toLowerCase().startsWith(pdisk) || path.toLowerCase().startsWith(dev);
  }

  /** Does the given string refer to a local drive / partition? */
  static boolean isPartition(String path) {
    return path.toLowerCase().startsWith("\\\\.\\") && path.toLowerCase().endsWith(":");
  }

  /**
   * Does the given drive path exist?
   *
   * @param path to drive
   * @return true if the drive exists, false otherwise
   */
  static boolean driveExists(String path) {
    // Test the drive by reading the first byte and checking if it's -1
    BufferedInputStream br = null;
    try {
      File tmp = new File(path);
      br = new BufferedInputStream(new FileInputStream(tmp));
      int b = br.read();
      if (b != -1) {
        return true;
      }
      return false;
    } catch (Exception ex) {
      return false;
    } finally {
      try {
        if (br != null) {
          br.close();
        }
      } catch (IOException ex) {
      }
    }
  }

  /**
   * Convert the Java timezone ID to the "formatted" string that can be accepted by the C/C++ code.
   * Example: "America/New_York" converted to "EST5EDT", etc
   *
   * @param timezoneID
   * @return
   */
  public static String convertTimeZone(String timezoneID) {
    String result = "";

    TimeZone zone = TimeZone.getTimeZone(timezoneID);
    int offset = zone.getRawOffset() / 1000;
    int hour = offset / 3600;
    int min = (offset % 3600) / 60;

    DateFormat dfm = new SimpleDateFormat("z");
    dfm.setTimeZone(zone);
    boolean hasDaylight = zone.useDaylightTime();
    String first =
        dfm.format(new GregorianCalendar(2010, 1, 1).getTime())
            .substring(0, 3); // make it only 3 letters code
    String second =
        dfm.format(new GregorianCalendar(2011, 6, 6).getTime())
            .substring(0, 3); // make it only 3 letters code
    int mid = hour * -1;
    result = first + Integer.toString(mid);
    if (min != 0) {
      result = result + ":" + Integer.toString(min);
    }
    if (hasDaylight) {
      result = result + second;
    }

    return result;
  }

  /* The methods below are used to manage the case directories (creating, checking, deleting, etc) */
  /**
   * to create the case directory
   *
   * @param caseDir Path to the case directory (typically base + case name)
   * @param caseName the case name (used only for error messages)
   * @throws CaseActionException throw if could not create the case dir @Deprecated
   */
  static void createCaseDirectory(String caseDir, String caseName) throws CaseActionException {
    createCaseDirectory(caseDir);
  }
  /**
   * Create the case directory and its needed subfolders.
   *
   * @param caseDir Path to the case directory (typically base + case name)
   * @throws CaseActionException throw if could not create the case dir
   */
  static void createCaseDirectory(String caseDir) throws CaseActionException {

    File caseDirF = new File(caseDir);
    if (caseDirF.exists()) {
      if (caseDirF.isFile()) {
        throw new CaseActionException(
            "Cannot create case dir, already exists and is not a directory: " + caseDir);
      } else if (!caseDirF.canRead() || !caseDirF.canWrite()) {
        throw new CaseActionException(
            "Cannot create case dir, already exists and cannot read/write: " + caseDir);
      }
    }

    try {
      boolean result = (caseDirF).mkdirs(); // create root case Directory
      if (result == false) {
        throw new CaseActionException("Cannot create case dir: " + caseDir);
      }

      // create the folders inside the case directory
      result =
          result
              && (new File(caseDir + File.separator + XMLCaseManagement.EXPORT_FOLDER_RELPATH))
                  .mkdir()
              && (new File(caseDir + File.separator + XMLCaseManagement.LOG_FOLDER_RELPATH)).mkdir()
              && (new File(caseDir + File.separator + XMLCaseManagement.TEMP_FOLDER_RELPATH))
                  .mkdir()
              && (new File(caseDir + File.separator + XMLCaseManagement.CACHE_FOLDER_RELPATH))
                  .mkdir();

      if (result == false) {
        throw new CaseActionException("Could not create case directory: " + caseDir);
      }

      final String modulesOutDir = caseDir + File.separator + getModulesOutputDirRelPath();
      result = new File(modulesOutDir).mkdir();
      if (result == false) {
        throw new CaseActionException(
            "Could not create modules output directory: " + modulesOutDir);
      }

    } catch (Exception e) {
      throw new CaseActionException("Could not create case directory: " + caseDir, e);
    }
  }

  /**
   * delete the given case directory
   *
   * @param casePath the case path
   * @return boolean whether the case directory is successfully deleted or not
   */
  static boolean deleteCaseDirectory(File casePath) {
    logger.log(Level.INFO, "Deleting case directory: " + casePath.getAbsolutePath());
    return FileUtil.deleteDir(casePath);
  }

  /** Invoke the creation of startup dialog window. */
  public static void invokeStartupDialog() {
    StartupWindowProvider.getInstance().open();
  }

  /** Call if there are no images in the case. Displays a dialog offering to add one. */
  private static void runAddImageAction() {
    SwingUtilities.invokeLater(
        new Runnable() {
          @Override
          public void run() {
            final AddImageAction action = Lookup.getDefault().lookup(AddImageAction.class);
            action.actionPerformed(null);
          }
        });
  }

  /**
   * Checks if a String is a valid case name
   *
   * @param caseName the candidate String
   * @return true if the candidate String is a valid case name
   */
  public static boolean isValidName(String caseName) {
    return !(caseName.contains("\\")
        || caseName.contains("/")
        || caseName.contains(":")
        || caseName.contains("*")
        || caseName.contains("?")
        || caseName.contains("\"")
        || caseName.contains("<")
        || caseName.contains(">")
        || caseName.contains("|"));
  }

  private static void clearTempFolder() {
    File tempFolder = new File(currentCase.getTempDirectory());
    if (tempFolder.isDirectory()) {
      File[] files = tempFolder.listFiles();
      if (files.length > 0) {
        for (int i = 0; i < files.length; i++) {
          if (files[i].isDirectory()) {
            deleteCaseDirectory(files[i]);
          } else {
            files[i].delete();
          }
        }
      }
    }
  }

  /**
   * Check for existence of certain case sub dirs and create them if needed.
   *
   * @param openedCase
   */
  private static void checkSubFolders(Case openedCase) {
    String modulesOutputDir = openedCase.getModulesOutputDirAbsPath();
    File modulesOutputDirF = new File(modulesOutputDir);
    if (!modulesOutputDirF.exists()) {
      logger.log(Level.INFO, "Creating modules output dir for the case.");

      try {
        if (!modulesOutputDirF.mkdir()) {
          logger.log(
              Level.SEVERE,
              "Error creating modules output dir for the case, dir: " + modulesOutputDir);
        }
      } catch (SecurityException e) {
        logger.log(
            Level.SEVERE,
            "Error creating modules output dir for the case, dir: " + modulesOutputDir,
            e);
      }
    }
  }

  // case change helper
  private static void doCaseChange(Case toChangeTo) {
    logger.log(Level.INFO, "Changing Case to: " + toChangeTo);
    if (toChangeTo != null) { // new case is open

      // clear the temp folder when the case is created / opened
      Case.clearTempFolder();
      checkSubFolders(toChangeTo);

      // enable these menus
      CallableSystemAction.get(AddImageAction.class).setEnabled(true);
      CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
      CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
      CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); // Delete Case menu

      if (toChangeTo.getRootObjectsCount() > 0) {
        // open all top components
        CoreComponentControl.openCoreWindows();
      } else {
        // close all top components
        CoreComponentControl.closeCoreWindows();
      }
    } else { // case is closed
      // close all top components first
      CoreComponentControl.closeCoreWindows();

      // disable these menus
      CallableSystemAction.get(AddImageAction.class).setEnabled(false); // Add Image menu
      CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); // Case Close menu
      CallableSystemAction.get(CasePropertiesAction.class)
          .setEnabled(false); // Case Properties menu
      CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); // Delete Case menu

      // clear pending notifications
      MessageNotifyUtil.Notify.clear();

      Frame f = WindowManager.getDefault().getMainWindow();
      f.setTitle(Case.getAppName()); // set the window name to just application name

      // try to force gc to happen
      System.gc();
      System.gc();
    }

    // log memory usage after case changed
    logger.log(Level.INFO, PlatformUtil.getAllMemUsageInfo());
  }

  // case name change helper
  private static void doCaseNameChange(String newCaseName) {
    // update case name
    if (!newCaseName.equals("")) {
      Frame f = WindowManager.getDefault().getMainWindow();
      f.setTitle(newCaseName + " - " + Case.getAppName()); // set the window name to the new value
    }
  }

  // delete image helper
  private void doDeleteImage() {
    // no more image left in this case
    if (currentCase.getRootObjectsCount() == 0) {
      // close all top components
      CoreComponentControl.closeCoreWindows();
    }
  }
}