// saves changes to player data. MUST be called after you're done making
  // changes, otherwise a reload will lose them
  @Override
  public synchronized void savePlayerData(String playerName, PlayerData playerData) {
    // never save data for the "administrative" account. an empty string for
    // claim owner indicates administrative account
    if (playerName.length() == 0) return;

    BufferedWriter outStream = null;
    try {
      // open the player's file
      File playerDataFile =
          new File(playerDataFolderPath + File.separator + playerName.toLowerCase());
      playerDataFile.createNewFile();
      outStream = new BufferedWriter(new FileWriter(playerDataFile));

      // first line is last login timestamp
      if (playerData.lastLogin == null) playerData.lastLogin = new Date();
      DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss");
      outStream.write(dateFormat.format(playerData.lastLogin));
      outStream.newLine();

      // second line is accrued claim blocks
      outStream.write(String.valueOf(playerData.accruedClaimBlocks));
      outStream.newLine();

      // third line is bonus claim blocks
      outStream.write(String.valueOf(playerData.bonusClaimBlocks));
      outStream.newLine();

      // fourth line is a double-semicolon-delimited list of claims
      /*if (playerData.claims.size() > 0) {
      	outStream.write(this.locationToString(playerData.claims.get(0).getLesserBoundaryCorner()));
      	for (int i = 1; i < playerData.claims.size(); i++) {
      		outStream.write(";;" + this.locationToString(playerData.claims.get(i).getLesserBoundaryCorner()));
      	}
      } */

      // write out wether the player's inventory needs to be cleared on join.
      outStream.newLine();
      outStream.write(String.valueOf(playerData.ClearInventoryOnJoin));
      outStream.newLine();
    }

    // if any problem, log it
    catch (Exception e) {
      GriefPrevention.AddLogEntry(
          "GriefPrevention: Unexpected exception saving data for player \""
              + playerName
              + "\": "
              + e.getMessage());
    }

    try {
      // close the file
      if (outStream != null) {
        outStream.close();
      }
    } catch (IOException exception) {
    }
  }
  @Override
  synchronized PlayerData getPlayerDataFromStorage(String playerName) {

    // if the file exists when we check for the specific casing, use that file.
    // On Windows machines the FS will not be case sensitive, however, for *nix based machines
    // the file systems and the file I/O API are case sensitive. We save data lowercase now
    // however previous installations may have upper-cased filenames. Thus we will
    // look for the filename for the file that it would be named if we create the path
    // with a case-insensitive player name.

    File CaseInsensitive = new File(getPlayerDataFile(playerName));
    File playerFile;
    playerFile = CaseInsensitive;
    PlayerData playerData = new PlayerData();
    playerData.playerName = playerName;

    // if it doesn't exist as a file
    if (!playerFile.exists()) {

      // create a file with defaults, but only if the player has been
      // online before.
      Player playerobj = Bukkit.getPlayer(playerName);
      if (playerobj == null) {
        this.savePlayerData(playerName, playerData);
      } else if (playerobj.hasPlayedBefore() || playerobj.isOnline()) {
        this.savePlayerData(playerName, playerData);
      }
    }

    // otherwise, read the file
    else {
      BufferedReader inStream = null;
      try {
        inStream = new BufferedReader(new FileReader(playerFile.getAbsolutePath()));

        // first line is last login timestamp
        String lastLoginTimestampString = inStream.readLine();

        // convert that to a date and store it
        DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss");
        try {
          playerData.lastLogin = dateFormat.parse(lastLoginTimestampString);
        } catch (ParseException parseException) {
          GriefPrevention.AddLogEntry(
              "Unable to load last login for \"" + playerFile.getName() + "\".");
          playerData.lastLogin = null;
        }

        // second line is accrued claim blocks
        String accruedBlocksString = inStream.readLine();

        // convert that to a number and store it
        playerData.accruedClaimBlocks = Integer.parseInt(accruedBlocksString);

        // third line is any bonus claim blocks granted by
        // administrators
        String bonusBlocksString = inStream.readLine();

        // convert that to a number and store it
        playerData.bonusClaimBlocks = Integer.parseInt(bonusBlocksString);

        // fourth line is a double-semicolon-delimited list of claims,
        // which is currently ignored

        try {
          inStream.readLine();
          String playerinventoryclear = inStream.readLine();
          playerData.ClearInventoryOnJoin = Boolean.parseBoolean(playerinventoryclear);
        } catch (Exception exx) {
        } // do nothing, seems like there was no value. Oh well.
        inStream.close();
      }

      // if there's any problem with the file's content, log an error
      // message
      catch (Exception e) {
        GriefPrevention.AddLogEntry("Unable to load data for player \"" + playerName + "\": ");
      }

      try {
        if (inStream != null) inStream.close();
      } catch (IOException exception) {
      }
    }

    return playerData;
  }