org.badvision.outlaweditor.data.DataUtilities.java Source code

Java tutorial

Introduction

Here is the source code for org.badvision.outlaweditor.data.DataUtilities.java

Source

/*
 * Copyright (C) 2015 The 8-Bit Bunch. Licensed under the Apache License, Version 1.1 
 * (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-1.1>.
 * 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 org.badvision.outlaweditor.data;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.xml.namespace.QName;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.badvision.outlaweditor.api.ApplicationState;
import org.badvision.outlaweditor.data.xml.Block;
import org.badvision.outlaweditor.data.xml.Field;
import org.badvision.outlaweditor.data.xml.Global;
import org.badvision.outlaweditor.data.xml.Map;
import org.badvision.outlaweditor.data.xml.NamedEntity;
import org.badvision.outlaweditor.data.xml.Scope;
import org.badvision.outlaweditor.data.xml.Script;
import org.badvision.outlaweditor.ui.UIAction;

public class DataUtilities {

    private DataUtilities() {
    }

    public static void ensureGlobalExists() {
        if (ApplicationState.getInstance().getGameData().getGlobal() == null) {
            ApplicationState.getInstance().getGameData().setGlobal(new Global());
        }
    }

    public static void sortMaps(List<? extends Map> entities) {
        if (entities == null) {
            return;
        }
        entities.sort((a, b) -> {
            String nameA = a == null ? "" : nullSafe(a.getName());
            String nameB = b == null ? "" : nullSafe(b.getName());
            if (nameA.equalsIgnoreCase("init")) {
                return -1;
            }
            if (nameB.equalsIgnoreCase("init")) {
                return 1;
            }
            return nameA.compareTo(nameB);
        });
    }

    public static void sortNamedEntities(List<? extends NamedEntity> entities) {
        if (entities == null) {
            return;
        }
        entities.sort((a, b) -> {
            String nameA = a == null ? "" : nullSafe(a.getName());
            String nameB = b == null ? "" : nullSafe(b.getName());
            if (nameA.equalsIgnoreCase("init")) {
                return -1;
            }
            if (nameB.equalsIgnoreCase("init")) {
                return 1;
            }
            return nameA.compareTo(nameB);
        });
    }

    public static String nullSafe(String str) {
        if (str == null) {
            return "";
        }
        return str;
    }

    public static String uppercaseFirst(String str) {
        StringBuilder b = new StringBuilder(str);
        int i = 0;
        do {
            b.replace(i, i + 1, b.substring(i, i + 1).toUpperCase());
            i = b.indexOf(" ", i) + 1;
        } while (i > 0 && i < b.length());
        return b.toString();
    }

    public static void cleanupScriptName(Script script) {
        if (script.getName() != null) {
            return;
        }
        extract(script.getBlock(), Field.class).filter((f) -> f.getName().equalsIgnoreCase("NAME")).findFirst()
                .ifPresent((f) -> script.setName(f.getValue()));
    }

    public static void cleanupScriptNames(Scope s) {
        if (s.getScripts() == null || s.getScripts().getScript() == null) {
            return;
        }
        s.getScripts().getScript().forEach(DataUtilities::cleanupScriptName);
    }

    public static void cleanupAllScriptNames() {
        cleanupScriptNames(ApplicationState.getInstance().getGameData().getGlobal());
        ApplicationState.getInstance().getGameData().getMap().forEach(DataUtilities::cleanupScriptNames);
    }

    public static <T> Optional<T> extractFirst(Block block, Class<T> desiredType) {
        return extract(block, desiredType).findFirst();
    }

    public static <T> Stream<T> extract(Block block, Class<T> desiredType) {
        if (block != null && block.getMutationOrFieldOrValue() != null) {
            return (Stream<T>) block.getMutationOrFieldOrValue().stream()
                    .filter((o) -> o.getClass().equals(desiredType));
        } else {
            return Stream.empty();
        }
    }

    public static String getValue(java.util.Map<QName, String> map, String name) {
        return map.entrySet().stream().filter((e) -> e.getKey().getLocalPart().equals(name)).map(e -> e.getValue())
                .findFirst().orElse(null);

    }

    public static void setValue(java.util.Map<QName, String> map, String name, String newValue) {
        Optional<java.util.Map.Entry<QName, String>> attr = map.entrySet().stream()
                .filter((e) -> e.getKey().getLocalPart().equals(name)).findFirst();
        if (attr.isPresent()) {
            attr.get().setValue(newValue);
        } else {
            map.put(new QName(name), newValue);
        }
    }

    public static List<List<String>> readFromFile(File file) {
        try {
            if (file.getName().toLowerCase().endsWith("txt") || file.getName().toLowerCase().endsWith("tsv")) {
                return readTextFile(file);
            } else if (file.getName().toLowerCase().endsWith("xls")) {
                return readLegacyExcel(file);
            } else if (file.getName().toLowerCase().endsWith("xlsx")) {
                return readExcel(file);
            }
        } catch (IOException | InvalidFormatException ex) {
            Logger.getLogger(DataUtilities.class.getName()).log(Level.SEVERE, null, ex);
        }
        UIAction.alert("Couldn't figure out how to import file " + file.getName());
        return Collections.EMPTY_LIST;
    }

    public static List<List<String>> readTextFile(File file) throws FileNotFoundException {
        BufferedReader reader = new BufferedReader(new FileReader(file));
        return reader.lines().map(line -> Arrays.asList(line.split("\\t"))).collect(Collectors.toList());
    }

    public static List<List<String>> readLegacyExcel(File file) throws FileNotFoundException, IOException {
        return readSheet(new HSSFWorkbook(new FileInputStream(file)));
    }

    public static List<List<String>> readExcel(File file)
            throws FileNotFoundException, IOException, InvalidFormatException {
        return readSheet(new XSSFWorkbook(file));
    }

    public static List<List<String>> readSheet(Workbook workbook) {
        Sheet sheet = workbook.getSheetAt(0);
        List<List<String>> data = new ArrayList<>();
        sheet.forEach(row -> {
            List<String> rowData = new ArrayList<>();
            row.forEach(cell -> {
                String col = getStringValueFromCell(cell);
                rowData.add(col);
            });
            data.add(rowData);
        });
        return data;
    }

    public static String getStringValueFromCell(Cell cell) {
        switch (cell.getCellType()) {
        case Cell.CELL_TYPE_BOOLEAN:
            return Boolean.toString(cell.getBooleanCellValue());
        case Cell.CELL_TYPE_BLANK:
            return null;
        case Cell.CELL_TYPE_NUMERIC:
            return Double.toString(cell.getNumericCellValue());
        case Cell.CELL_TYPE_STRING:
            return cell.getStringCellValue();
        default:
            return "???";
        }
    }

    public static String hexDump(byte[] data) {
        StringBuilder dump = new StringBuilder();
        for (int i = 0; i < data.length; i++) {
            if (i > 0) {
                dump.append(",");
            }
            dump.append(getHexValueFromByte(data[i]));
        }
        return dump.toString();
    }

    public static String getHexValueFromByte(byte val) {
        return getHexValue(val & 0x0ff);
    }

    public static String getHexValue(int val) {
        if (val < 16) {
            return "0" + Integer.toHexString(val);
        } else {
            return Integer.toHexString(val);
        }
    }

    //------------------------------ String comparators
    /**
     * Rank two strings similarity in terms of distance The lower the number,
     * the more similar these strings are to each other See:
     * http://en.wikipedia.org/wiki/Levenshtein_distance#Computing_Levenshtein_distance
     *
     * @param s
     * @param t
     * @param limit
     * @return Distance (higher is better)
     */
    public static int levenshteinDistance(String s, String t, int limit) {
        int sizeDiff = Math.abs(s.length() - t.length());
        if (sizeDiff > limit) {
            return sizeDiff;
        }

        s = s.toLowerCase().replaceAll("[^a-zA-Z0-9\\s]", "");
        t = t.toLowerCase().replaceAll("[^a-zA-Z0-9\\s]", "");
        int m = s.length();
        int n = t.length();
        int[][] dist = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            dist[i][0] = i;
        }
        for (int i = 1; i <= n; i++) {
            dist[0][i] = i;
        }
        for (int j = 1; j <= n; j++) {
            int min = 100;
            for (int i = 1; i <= m; i++) {
                if (s.charAt(i - 1) == t.charAt(j - 1)) {
                    dist[i][j] = dist[i - 1][j - 1];
                } else {
                    int del = dist[i - 1][j] + 1;
                    int insert = dist[i][j - 1] + 1;
                    int sub = dist[i - 1][j - 1] + 1;
                    dist[i][j] = Math.min(Math.min(del, insert), sub);
                }
                min = Math.min(min, dist[i][j]);
            }
            if (min > limit) {
                return min;
            }
        }
        return dist[m][n];
    }

    /**
     * Compare strings based on a tally of similar patterns found, using a fixed
     * search window The resulting score is heavily penalized if the strings
     * differ greatly in length This is not as efficient as levenshtein, so it's
     * only used as a tie-breaker.
     *
     * @param c1
     * @param c2
     * @param width Search window size
     * @return Overall similarity score (higher is better)
     */
    public static double rankMatch(String c1, String c2, int width) {
        double score = 0;
        String s1 = c1.toLowerCase();
        String s2 = c2.toLowerCase();
        for (int i = 0; i < s1.length() + 1 - width; i++) {
            String m = s1.substring(i, i + width);
            int j = 0;
            while ((j = s2.indexOf(m, j)) > -1) {
                score += width;
                j++;
            }
        }
        double l1 = s1.length();
        double l2 = s2.length();
        // If the two strings are equivilent in length, the score is higher
        // If the two strings are different in length, the score is adjusted lower depending on how large the difference is
        // This is offset just a hair for tuning purposes
        double adjustment = (Math.min(l1, l2) / Math.max(l1, l2)) + 0.1;
        return score * adjustment * adjustment;
    }

    public static class RankingComparator implements Comparator<String> {

        String match;

        public RankingComparator(String match) {
            // Adding a space helps respect word boundaries as part of the match
            // In the case of very close matches this is another tie-breaker
            // Especially for very small search terms
            this.match = match + " ";
        }

        @Override
        public int compare(String o1, String o2) {
            double s1 = levenshteinDistance(match, o1, 20);
            double s2 = levenshteinDistance(match, o2, 20);
            if (s2 == s1) {
                s1 = rankMatch(o1, match, 3) + rankMatch(o1, match, 2);
                s2 = rankMatch(o2, match, 3) + rankMatch(o2, match, 2);
                if (s2 == s1) {
                    return (o1.compareTo(o2));
                } else {
                    // Normalize result to -1, 0 or 1 so there is no rounding issues!
                    return (int) Math.signum(s2 - s1);
                }
            } else {
                return (int) (s2 - s1);
            }
        }
    }

    /**
     * Given a desired search string and a search space of recognized
     * selections, identify the best match in the list
     *
     * @param match String to search for
     * @param search Space of all valid results
     * @return Best match found, or null if there was nothing close to a match
     * found.
     */
    public static String findBestMatch(String match, Collection<String> search) {
        if (search == null || search.isEmpty()) {
            return null;
        }
        RankingComparator r = new RankingComparator(match);
        List<String> candidates = new ArrayList<>(search);
        Collections.sort(candidates, r);
        double score = levenshteinDistance(match, candidates.get(0), 20);
        if (score > 1) {
            return candidates.get(0);
        }
        return null;
    }
}