/*
* This file is part of the Illarion project.
*
* Copyright © 2014 - Illarion e.V.
*
* Illarion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Illarion 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.
*/
package illarion.easynpc;
import illarion.common.util.CopyrightHeader;
import illarion.easynpc.gui.Editor;
import javax.annotation.Nonnull;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This class contains the script data to a easyNPC script. It contains the
* plain unparsed script split in logical groups.
*
* @author Martin Karing <nitram@illarion.org>
*/
public final class EasyNpcScript {
/**
* The default charset that is used to read and write the easyNPC script files.
*/
@Nonnull
public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
/**
* The copyright header of the easyNPC writer.
*/
@Nonnull
public static final CopyrightHeader COPYRIGHT_HEADER = new CopyrightHeader(80, null, null, "-- ", null);
/**
* The representation of each line in the easyNPC script. It stores the line number and the text of the lines.
*/
public static final class Line {
/**
* The text of this line.
*/
private final String line;
/**
* The line number of this line.
*/
private final int lineNumber;
/**
* Create a new line representation with the line number and the text of
* the line stored in it.
*
* @param number the line number
* @param text the text of the line
*/
Line(int number, String text) {
lineNumber = number;
line = text;
}
/**
* The text of this script line.
*
* @return the text of the line
*/
public String getLine() {
return line;
}
/**
* Get the line number of this script line.
*
* @return the line number
*/
public int getLineNumber() {
return lineNumber;
}
}
/**
* The leading characters for Lua comments.
*/
private static final String LUA_COMMENT_LEAD = "--";
/**
* The original encoding used by this script.
*/
private Charset encoding;
/**
* This list contains all entries in this script written in the order as
* they are written in the script.
*/
@Nonnull
private final List<Line> entries;
/**
* The file that script was load from.
*/
private Path sourceScriptFile;
/**
* The editor this easyNPC script was generated from.
*/
private Editor sourceEditor;
/**
* Create a new, empty EasyNPC Script. This scripts waits to be filled with
* new entries.
*/
private EasyNpcScript() {
entries = new ArrayList<>();
}
/**
* Read a EasyNPC Script from a specified file. This writes this script into the data storage of this class and
* prepares it to be handled by the parser.
*
* @param sourceFile the file that is read to get the easyNPC data
* @throws IOException thrown in case anything goes wrong while reading this file.
*/
public EasyNpcScript(@Nonnull Path sourceFile) throws IOException {
this();
readFromInputStream(sourceFile);
}
/**
* Get one entry load from the script file.
*
* @param index The index of the entry
* @return the entry from the script file on the given index
*/
public Line getEntry(int index) {
return entries.get(index);
}
/**
* Get the amount of entries currently load into this easyNPC script.
*
* @return the amount of entries load in this script.
*/
public int getEntryCount() {
return entries.size();
}
/**
* Get the encoding of this script.
*
* @return the encoding of this script
*/
public Charset getScriptEncoding() {
return encoding;
}
/**
* Get the file this script was load from in case there is any.
*
* @return the script this file was load from
*/
public Path getSourceScriptFile() {
return sourceScriptFile;
}
/**
* Get the source editor that supplied the data for this script.
*
* @return the source editor of this script
*/
public Editor getSourceEditor() {
return sourceEditor;
}
/**
* Read the NPC script from a physical file.
*
* @param sourceFile the file that is read
* @throws IOException error thrown in case reading failed
*/
void readFromInputStream(@Nonnull Path sourceFile) throws IOException {
if (Files.isDirectory(sourceFile) || !Files.isReadable(sourceFile)) {
throw new FileNotFoundException(sourceFile.toString());
}
if (readNPCScript(sourceFile)) {
sourceScriptFile = sourceFile;
return;
}
throw new IOException("Can't read file: " + sourceFile);
}
/**
* Read the NPC script from a string source.
*
* @param source The entire script to read
*/
void readNPCScript(@Nonnull String source) {
String[] lines = source.split("\n");
readNPCScript(Arrays.asList(lines));
}
/**
* Read the NPC script data from a editor.
*
* @param editor the editor that supplies the script data
*/
public void readFromEditor(@Nonnull Editor editor) {
sourceEditor = editor;
readNPCScript(editor.getScriptText());
}
/**
* Write the content of this script in the proper format to the disk.
*
* @param targetFile the file that is supposed to store the new written
* script
* @throws IOException thrown in case there is a problem while writing
*/
public void writeNPCScript(@Nonnull Path targetFile) throws IOException {
try (BufferedWriter writer = Files.newBufferedWriter(targetFile, DEFAULT_CHARSET)) {
for (Line line : entries) {
writer.write(line.getLine());
writer.newLine();
}
writer.flush();
}
}
/**
* Read the easyNPC script from a file.
*
* @param sourceFile the file to read
* @return {@code true} in case everything worked
* @throws IOException thrown in case the reading operation failed
*/
private boolean readNPCScript(@Nonnull Path sourceFile) {
try {
List<String> lineList = Files.readAllLines(sourceFile, DEFAULT_CHARSET);
encoding = DEFAULT_CHARSET;
readNPCScript(lineList);
return true;
} catch (IOException e) {
// nothing
}
for (Charset set : Charset.availableCharsets().values()) {
try {
List<String> lineList = Files.readAllLines(sourceFile, set);
encoding = set;
readNPCScript(lineList);
return true;
} catch (IOException e) {
// nothing
}
}
return false;
}
/**
* Read a NPC script from a set of lines. The lines need to be stored on by one in a array of strings.
*
* @param lines The array of lines
*/
private void readNPCScript(@Nonnull Iterable<String> lines) {
boolean currentlyCommentBlock = false;
boolean currentlyEmptyBlock = false;
int lineNumber = 0;
for (String orgLine : lines) {
String line = orgLine.trim();
lineNumber++;
if (line.isEmpty()) {
if (!currentlyEmptyBlock) {
entries.add(new Line(lineNumber, line));
}
currentlyEmptyBlock = true;
currentlyCommentBlock = false;
} else if (line.startsWith(LUA_COMMENT_LEAD)) {
if (currentlyCommentBlock) {
Line lastLine = entries.remove(entries.size() - 1);
String newLine = lastLine.getLine() + '\n' + line;
if (COPYRIGHT_HEADER.isLicenseText(newLine)) {
currentlyCommentBlock = false;
currentlyEmptyBlock = false;
continue;
} else {
entries.add(new Line(lastLine.getLineNumber(), lastLine.getLine() + '\n' + line));
}
} else {
entries.add(new Line(lineNumber, line));
}
currentlyCommentBlock = true;
currentlyEmptyBlock = false;
} else {
entries.add(new Line(lineNumber, line));
currentlyCommentBlock = false;
currentlyEmptyBlock = false;
}
}
if (!currentlyEmptyBlock) {
entries.add(new Line(lineNumber + 1, ""));
}
}
}