package wf.doyle.blockbuster.file;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import com.google.common.base.CaseFormat;
import wf.doyle.blockbuster.App;
import wf.doyle.blockbuster.item.LibraryItem;
import wf.doyle.blockbuster.util.EnumLineType;
* Provides functions to read and parse provided files.
*
* @author Jordan Doyle
*/
public class FileReader {
* Path to the file to read
*/
private Path path;
* Enum containing the current line type.
*/
private EnumLineType currentLine = EnumLineType.NONE;
* Pattern to find data types
*/
private Pattern dataTypes = Pattern.compile("\\[(.*) data\\]");
* Line number we're currently dealing with.
*/
private int lineNumber = 0;
* A list of what the current line type gives us, allows easy setting with
* reflection
*/
private String[] lineContains;
* Contains a list of what each line type should contain which lets us write
* data back to the file easily.
*/
public static Map<String, String[]> containing = new HashMap<String, String[]>();
* Contains a mapping of EnumLineTypes to the respective classes for use in
* {@link #wf.doyle.blockbuster.item.LibraryItem.toString()}
*/
public static Map<EnumLineType, Class<? extends LibraryItem>> classes = new HashMap<EnumLineType, Class<? extends LibraryItem>>();
* Instantiates a new instance of FileReader
*
* @param path
* path of the file to read
*/
public FileReader(Path path)
{
this.path = path;
}
* Parses the current file
*/
public void parseFile()
{
try(Stream<String> lines = Files.lines(this.path, StandardCharsets.UTF_8))
{
Iterator<String> it = lines.iterator();
while(it.hasNext())
{
this.lineNumber++;
String line = it.next();
if(this.currentLine == EnumLineType.UNKNOWN) continue;
parseLine(line.trim());
}
}
catch(IOException e)
{
App.LOGGER.error("Error whilst reading provided file", e);
}
}
* @return storage class from current line type
*/
private Class<? extends LibraryItem> getClassType()
{
Class<? extends LibraryItem> classType = null;
try
{
classType = (Class<? extends LibraryItem>) Class.forName("wf.doyle.blockbuster.item.items.audiovisual." + this.currentLine.toString());
}
catch(ClassNotFoundException e)
{
try
{
classType = (Class<? extends LibraryItem>) Class.forName("wf.doyle.blockbuster.item.items.printed." + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, this.currentLine.toString()));
}
catch(ClassNotFoundException ex)
{
try
{
classType = (Class<? extends LibraryItem>) Class.forName("wf.doyle.blockbuster.item.items.audiovisual." + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, this.currentLine.toString()));
}
catch(ClassNotFoundException ex2)
{
App.LOGGER.error("Couldn't find storage class for this line type.", ex2);
}
}
}
return classType;
}
* Find a private field in a class (and its parents)
*
* @param nameRaw
* name of field to find
* @param classType
* class to find the field in
* @return the found private field
*/
public static Field getField(String nameRaw, Class<? extends LibraryItem> classType)
{
String name = nameRaw.trim();
Field field = null;
try
{
field = classType.getSuperclass().getSuperclass().getDeclaredField(name);
}
catch(NoSuchFieldException | SecurityException e)
{
try
{
field = classType.getSuperclass().getDeclaredField(name);
}
catch(NoSuchFieldException | SecurityException ex)
{
try
{
field = classType.getDeclaredField(name);
}
catch(NoSuchFieldException | SecurityException ex2)
{
App.LOGGER.error("Couldn't find field " + name + " in " + classType.getName(), ex2);
}
}
}
return field;
}
* Set the value of a private field
*
* @param f
* field to set the value of
* @param instance
* instance to set the value in
* @param data
* data to set the value as
*/
public static void setField(Field f, LibraryItem instance, String data)
{
try
{
try
{
if(f.getType().isAssignableFrom(int.class))
{
int val = (int) Double.parseDouble(data);
f.setAccessible(true);
f.set(instance, val);
return;
}
}
catch(NumberFormatException nfe)
{
}
if(data.equalsIgnoreCase("true") || data.equalsIgnoreCase("false"))
{
boolean b = Boolean.parseBoolean(data);
f.setAccessible(true);
f.set(instance, b);
return;
}
f.setAccessible(true);
f.set(instance, data);
}
catch(IllegalArgumentException | IllegalAccessException e)
{
App.LOGGER.error("Couldn't set value of " + f.getName() + " in " + instance.getClass().getName(), e);
}
return;
}
* Parses the current line and decides what to do with it.
*
* @param line
* string to parse
*/
private void parseLine(String line)
{
if(line.isEmpty() || line.startsWith("//"))
{
if(line.startsWith("// data is")) this.lineContains = line.substring(line.indexOf("// data is") + "// data is".length()).split(", ");
return;
}
if(determineLineType(line)) return;
Class<? extends LibraryItem> itemClass = this.getClassType();
if(!containing.containsKey(itemClass.getName())) containing.put(itemClass.getName(), this.lineContains);
if(!classes.containsKey(this.currentLine)) classes.put(this.currentLine, itemClass);
try
{
LibraryItem item = itemClass.newInstance();
ListIterator<String> iterator = Arrays.asList(line.split(",")).listIterator();
while(iterator.hasNext())
{
int key = iterator.nextIndex();
String value = iterator.next().trim();
Field field = getField(this.lineContains[key].trim(), itemClass);
FileReader.setField(field, item, value);
}
App.items.add(item);
}
catch(IllegalAccessException | InstantiationException e)
{
App.LOGGER.error("Couldn't instantiate storage class.", e);
}
}
* Parses the current line and determines the type of data it is providing
*
* @param line
* string to parse to type
* @return true if found a line type
*/
private boolean determineLineType(String line)
{
Matcher m = this.dataTypes.matcher(line);
if(!m.find()) return false;
switch(m.group(1).toLowerCase())
{
case "dvd":
this.currentLine = EnumLineType.DVD;
return true;
case "cd":
this.currentLine = EnumLineType.CD;
return true;
case "book":
this.currentLine = EnumLineType.BOOK;
return true;
case "periodical":
this.currentLine = EnumLineType.PERIODICAL;
return true;
default:
this.currentLine = EnumLineType.UNKNOWN;
App.LOGGER.error("Couldn't recognise data type on line " + this.lineNumber + " in file " + this.path.getFileName() + ", skipping this block.");
return true;
}
}
}