org.darkstar.batch.TextIdUpdate.java Source code

Java tutorial

Introduction

Here is the source code for org.darkstar.batch.TextIdUpdate.java

Source

package org.darkstar.batch;

/** 
 * Copyright (c) 2010-2014 Darkstar Dev Teams
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/
 * 
 * This file is part of DarkStar-server source code.
 */

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.darkstar.batch.utils.DarkstarUtils;

/**
 * Class to Perform a Batch Update of Text IDs after a Version Update
 * 
 * Requires a POLUtils Dump of dialog tables for reference. This job
 * makes comparisons between the comment in the TextID file and the
 * dialog tables in POLUtils to find the updated Text ID
 * 
 * Accuracy varies with two factors:
 * 
 * 1) Accuracy of the comment in the TextID files. Typos will break the
 * comparison
 * 
 * 2) State of bad chars & etc. in the dialog table dumps. This job will
 * try to strip them out of both sides for comparison, but that may leave
 * the sting without enough context to make it unique. In this case, its
 * possible the wrong ID would be selected.
 * 
 * Ids not concretely matched can be prepended with FIXME: for manual review.
 * 
 * Please check for these after the job and fix them before committing.
 */
public class TextIdUpdate {

    private static final Logger LOG = Logger.getLogger(TextIdUpdate.class);

    private Map<String, Integer> fixmeCountMap;
    private Properties configProperties;
    private int errors = 0;
    private int guesses = 0;
    private boolean markGuesses;
    private boolean writeFilteredDialogTables;

    /**
     * Batch Entry Point for Text ID Updates
     * @param args
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {
        try {
            final TextIdUpdate textIdUpdate = new TextIdUpdate();
            textIdUpdate.updateTextIds();
        } catch (final Exception e) {
            LOG.error("Error Updating Text IDs", e);
        }
    }

    /**
     * Method to Run a Mass Text ID Update
     */
    private void updateTextIds() {
        configProperties = DarkstarUtils.loadBatchConfiguration();
        markGuesses = Boolean.valueOf(configProperties.getProperty("textIdMarkGuesses", "true"));
        writeFilteredDialogTables = Boolean
                .valueOf(configProperties.getProperty("writeFilteredDialogTables", "true"));
        fixmeCountMap = new HashMap<>();

        final int minZone = Integer.valueOf(configProperties.getProperty("minZoneId", "0"));
        final int maxZone = Integer.valueOf(configProperties.getProperty("maxZoneId", "255"));

        for (int zoneId = minZone; zoneId <= maxZone; zoneId++) {
            if (DarkstarUtils.isZoneMapped(configProperties, zoneId)) {
                LOG.info(String.format("Processing Zone ID <%d>...", zoneId));
                updateTextIdsForZone(zoneId);
            } else {
                LOG.info(String.format("Zone ID <%d> Is Not Configured, Skipping...", zoneId));
            }
        }

        LOG.info(String.format("A Total of <%d> Guesses Occurred.", guesses));

        if (markGuesses) {
            LOG.info("Guesses are marked with \"FIXME:\". Please Manually Review Them Before Commiting");
            LOG.info("Here are the \"FIXME:\" Counts:");

            final Set<String> fixmeKeySet = fixmeCountMap.keySet();
            for (final String fixmeKey : fixmeKeySet) {
                final Integer fixmeValue = fixmeCountMap.get(fixmeKey);

                if (fixmeValue != null && fixmeValue > 0) {
                    LOG.info(String.format("File <%s> contains <%d> FIXMEs", fixmeKey, fixmeValue));
                }
            }
        }

        LOG.info(String.format("A Total of <%d> Unhandled Errors Occurred.", errors));
    }

    /**
     * Method to Update Text IDs for a Single Zone
     * @param zoneId Zone ID to Update
     */
    private void updateTextIdsForZone(final int zoneId) {
        final String textIdFilePath = DarkstarUtils.getTextIdFilePath(configProperties, zoneId);
        final File textIdFile = new File(textIdFilePath);
        final File polUtilsDialogTableFile = new File(
                DarkstarUtils.getDialogTablePathByZoneId(configProperties, zoneId));
        ;

        // Read the Dialog Table File, and Keep Both & Original Read of it and a Filtered Read
        String polUtilsString;

        try {
            // Shifting Encoding to Avoid Issues w/ Regex Replace All
            polUtilsString = FileUtils.readFileToString(polUtilsDialogTableFile, "Shift_JIS");
            polUtilsString = DarkstarUtils.collapseAndFormatXmlString(polUtilsString);
            byte[] polUtilsShiftJisBytes = polUtilsString.getBytes("Shift_JIS");
            Charset shiftJis = Charset.forName("Shift_JIS");
            Charset ansi = Charset.forName("Cp1252");
            CharBuffer shiftJisCharBuffer = shiftJis.decode(ByteBuffer.wrap(polUtilsShiftJisBytes));
            ByteBuffer ansiByteBuffer = ansi.encode(shiftJisCharBuffer);
            polUtilsString = new String(ansiByteBuffer.array(), "Cp1252");
        } catch (IOException e) {
            LOG.error(String.format("Zone ID <%d> -> Could Not Read Dialog Table, Skipping...", zoneId), e);
            errors++;
            return;
        }

        final String filteredPolUtilsString = DarkstarUtils.filterBadCharacters(polUtilsString);

        // Optional: Write Filtered Dialog Tables. Helps you figure out how you can make a TextID auto-update in the future.
        if (writeFilteredDialogTables) {
            DarkstarUtils.writeFilteredDialogTable(configProperties, filteredPolUtilsString, zoneId);
        }

        // Read the Text ID File
        List<String> textIdLines;

        try {
            textIdLines = FileUtils.readLines(textIdFile, "UTF-8");
        } catch (IOException e) {
            LOG.error(String.format("Zone ID <%d> -> Could Not Read Text ID File, Skipping...", zoneId), e);
            errors++;
            return;
        }

        int zoneGuesses = 0;

        for (int lineIndex = 0; lineIndex < textIdLines.size(); lineIndex++) {
            String textIdLine = textIdLines.get(lineIndex);

            // If we don't have all 3 of these markers on a line, then the line is not a text id definition.
            if (textIdLine.indexOf("=") == -1 || textIdLine.indexOf(";") == -1) {
                continue;
            }

            String comment = "";

            // Search for Markers on this Line
            final int lineEqual = textIdLine.indexOf("=");
            final int semiColon = textIdLine.indexOf(";", lineEqual);

            // Capture the Comment
            final String variable = textIdLine.substring(0, lineEqual).trim();

            // Capture the Current Id
            final int id = Integer.valueOf(textIdLine.substring(lineEqual + 1, semiColon).trim());

            boolean appendComment = false;

            if (textIdLine.indexOf("--") >= 0) {
                final int commentIndex = textIdLine.indexOf("--");
                //    Capture the Comment         
                comment = textIdLine.substring(commentIndex + 2).trim();
            } else {
                comment = configProperties.getProperty(variable);
                if (comment == null) {
                    comment = "@UNKNOWN_GLOBAL@";
                } else {
                    appendComment = true;
                }
            }

            // Ignore Unknowns & Current ID 0's
            if ("??? message".equals(comment) || "[UNKNOWN]".equals(comment) || id == 0) {
                continue;
            }

            // Apply some basic filtering. These are showing up as a single ? in POLUtils right now,
            // while they are filled out in most of our Text ID Luas. So we need to sync up before
            // comparing. Since this is very common, we will not treat this as triggering a guess.
            String searchComment = comment;
            searchComment = searchComment.replaceAll("(<<<)", "<");
            searchComment = searchComment.replaceAll("(>>>)", ">");
            searchComment = searchComment.replace('<', '?');
            searchComment = searchComment.replace('>', '?');

            // Hack for Case Mismatching on This, Us Vs. POLUtils in a lot of cases
            if ("CHEST_FAIL".equals(variable)) {
                searchComment = searchComment.toLowerCase(Locale.US);
            }

            // We're going to capture where the current position in Dialog Utils, and start our search from there. 
            // Helps keep the right ID when there a multiple similar lines
            final String lastSearchIndexString = DarkstarUtils.FIELD_INDEX_OPENING_TAG + id
                    + DarkstarUtils.FIELD_CLOSING_TAG;
            int lastIndex = polUtilsString.indexOf(lastSearchIndexString);
            int lastFilteredIndex = filteredPolUtilsString.indexOf(lastSearchIndexString);

            // Run initial search. If this matches, its considered a match & not a guess.
            boolean guess = false;
            boolean filtered = false;
            LOG.debug("Searching: " + searchComment);

            int polUtilsIndex = polUtilsString.indexOf(searchComment, lastIndex);

            // If we couldn't find it, we'll have to apply bad char filtering
            if (polUtilsIndex == -1) {
                searchComment = DarkstarUtils.filterBadCharacters(searchComment).trim();
                LOG.debug("Searching (Filtered): " + searchComment);
                polUtilsIndex = filteredPolUtilsString.indexOf(searchComment, lastFilteredIndex);
                guess = false;
                filtered = true;
            }

            // If we couldn't find it again, see if there's a manually specified version in the properties file
            if (polUtilsIndex == -1) {
                searchComment = configProperties.getProperty(variable);

                if (searchComment != null) {
                    LOG.debug("Searching (Manual): " + searchComment);
                    polUtilsIndex = filteredPolUtilsString.indexOf(searchComment, lastFilteredIndex);
                    guess = false; // Doesn't count as a guess since someone manually set the criteria
                    filtered = true;
                }
            }

            // If we've found something...
            if (polUtilsIndex >= 0) {
                // Capture the New ID Value
                int fieldIndex;
                int fieldEndIndex;
                int fieldValue;

                if (filtered) {
                    fieldIndex = filteredPolUtilsString.lastIndexOf(DarkstarUtils.FIELD_INDEX_OPENING_TAG,
                            polUtilsIndex);
                    fieldEndIndex = filteredPolUtilsString.indexOf(DarkstarUtils.FIELD_CLOSING_TAG, fieldIndex);
                    fieldValue = Integer.valueOf(filteredPolUtilsString
                            .substring(fieldIndex + DarkstarUtils.FIELD_INDEX_OPENING_TAG.length(), fieldEndIndex));
                } else {
                    fieldIndex = polUtilsString.lastIndexOf(DarkstarUtils.FIELD_INDEX_OPENING_TAG, polUtilsIndex);
                    fieldEndIndex = polUtilsString.indexOf(DarkstarUtils.FIELD_CLOSING_TAG, fieldIndex);
                    fieldValue = Integer.valueOf(polUtilsString
                            .substring(fieldIndex + DarkstarUtils.FIELD_INDEX_OPENING_TAG.length(), fieldEndIndex));
                }

                // If the new Id & Old Id are the same, leave it alone
                if (id == fieldValue) {
                    guess = false;
                    LOG.debug(String.format("%s: %d -> %d, Same Id Detected, Leaving it alone...", variable, id,
                            fieldValue));
                }
                // Update it
                else {
                    textIdLine = textIdLine.replaceAll("(" + id + ")", String.valueOf(fieldValue));

                    if (appendComment) {
                        textIdLine = textIdLine + " -- " + comment;
                    }

                    textIdLines.set(lineIndex, textIdLine);
                    LOG.info(String.format("%s: %d -> %d", variable, id, fieldValue));
                }
            }
            // If we've got nothing and we're marking stuff, flag it with FIXME for manual review. Otherwise we're leaving it alone.
            else if (markGuesses) {
                textIdLine = DarkstarUtils.FIX_ME + textIdLine;
                textIdLines.set(lineIndex, textIdLine);
                LOG.info(String.format(
                        "%s: %d -> ???, Couldn't Find Any Matches, Marking for Manual Review With FIXME...",
                        variable, id));
                guess = true;
            } else {
                LOG.info(String.format("%s: %d -> ???, Couldn't Find Any Matches, So We Left it Alone...", variable,
                        id));
            }

            if (guess) {
                zoneGuesses++;
            }
        }

        // Write Text ID File
        try {
            FileUtils.writeLines(textIdFile, textIdLines);
        } catch (IOException e) {
            LOG.error(String.format("Zone ID <%d> -> Could Write Text ID File, Skipping...", zoneId), e);
            errors++;
            return;
        }

        // Record Fixme / Guess Data
        fixmeCountMap.put(textIdFilePath, zoneGuesses);
        guesses += zoneGuesses;

    }

}