diff --git a/src/main/java/_8/Lecture.java b/src/main/java/_8/Lecture.java index f3d04ae..9816c8c 100644 --- a/src/main/java/_8/Lecture.java +++ b/src/main/java/_8/Lecture.java @@ -1,5 +1,6 @@ package _8; +import _8.io.SonReader; import _8.io.SonWriter; import java.io.*; @@ -27,6 +28,8 @@ public class Lecture { this.semester = semester; } + public Lecture() {} + public String getNumber() { return number; } @@ -320,4 +323,90 @@ public class Lecture { out.endObject(); } + + public static Lecture loadText2(String filename) throws IOException { + Path file = Paths.get(filename); + try(SonReader in = new SonReader(Files.newBufferedReader(file))) { + return loadSon(in); + } + } + + public static Lecture loadSon(SonReader in) throws IOException { + in.beginObject(); + Lecture lecture = new Lecture(); + while(in.hasNext()) { + switch(in.nextName()) { + case "number": + lecture.setNumber(in.nextValue()); + break; + case "title": + lecture.setTitle(in.nextValue()); + break; + case "shortTitle": + lecture.setShortTitle(in.nextValue()); + break; + case "semester": + lecture.setSemester(in.nextValue()); + break; + case "lecturers": + in.beginArray(); + while(in.hasNext()) { + in.beginObject(); + Lecturer lecturer = new Lecturer(); + while(in.hasNext()) { + switch(in.nextName()) { + case "firstName": + lecturer.setFirstName(in.nextValue()); + break; + case "lastName": + lecturer.setLastName(in.nextValue()); + break; + default: + in.skipValue(); + break; + } + } + in.endObject(); + lecture.addLecturers(lecturer); + } + in.endArray(); + break; + case "schedule": + in.beginArray(); + while(in.hasNext()) { + in.beginObject(); + Date scheduleEntry = new Date(); + while(in.hasNext()) { + switch(in.nextName()) { + case "time": + applyDate(scheduleEntry, in.nextValue()); + break; + case "lectureHall": + scheduleEntry.setLectureHall(in.nextValue()); + break; + case "topics": + in.beginArray(); + while(in.hasNext()) { + scheduleEntry.addTopics(in.nextValue()); + } + in.endArray(); + break; + default: + in.skipValue(); + break; + } + } + in.endObject(); + lecture.addSchedule(scheduleEntry); + } + in.endArray(); + break; + default: + in.skipValue(); + break; + } + } + in.endObject(); + return lecture; + } } diff --git a/src/main/java/_8/io/SonReader.java b/src/main/java/_8/io/SonReader.java new file mode 100644 index 0000000..8dd4e8a --- /dev/null +++ b/src/main/java/_8/io/SonReader.java @@ -0,0 +1,228 @@ +package _8.io; + +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.io.Reader; +import java.util.Arrays; + +import static _8.io.SonState.*; + +public class SonReader implements Closeable { + + private final Reader in; + + private SonState[] stack = new SonState[32]; + private int stackTop = 0; + + private Integer rememberedChar; + + public SonReader(Reader in) { + this.in = in; + this.pushState(EMPTY_DOCUMENT); + } + + public void beginObject() throws IOException { + this.prepareValue(); + if(this.nextNonWhitespace() == '{') { + this.pushState(EMPTY_OBJECT); + } else { + throw new IllegalStateException("Expected BEGIN_OBJECT"); + } + } + + public void endObject() throws IOException { + if(this.nextNonWhitespace() == '}') { + this.popState(); + } else { + throw new IllegalStateException("Expected END_OBJECT"); + } + } + + public void beginArray() throws IOException { + this.prepareValue(); + if(this.nextNonWhitespace() == '[') { + this.pushState(EMPTY_ARRAY); + } else { + throw new IllegalStateException("Expected BEGIN_ARRAY"); + } + } + + public void endArray() throws IOException { + if(this.nextNonWhitespace() == ']') { + this.popState(); + } else { + throw new IllegalStateException("Expected END_ARRAY"); + } + } + + public boolean hasNext() throws IOException { + int c = this.nextNonWhitespace(); + this.rememberChar(c); + switch(this.peekState()) { + case EMPTY_DOCUMENT: + case EMPTY_OBJECT: + case NONEMPTY_DOCUMENT: + case NONEMPTY_OBJECT: + return c != '}'; + case EMPTY_ARRAY: + case NONEMPTY_ARRAY: + return c != ']'; + case DANGLING_NAME: + default: + throw new IllegalStateException(); + } + } + + public String nextName() throws IOException { + return this.readName(); + } + + public String nextValue() throws IOException { + return this.readValue(); + } + + private String readName() throws IOException { + this.prepareName(); + + final StringBuilder name = new StringBuilder(); + int c = this.nextNonWhitespace(); + do { + name.append((char) c); + } while((c = this.eofRead()) != ':'); + return name.toString(); + } + + private void prepareName() throws IOException { + SonState state = this.peekState(); + if(state == NONEMPTY_OBJECT) { // not first in an object + this.nextChar(true);// remove , from previous value + } else if(state != EMPTY_OBJECT) { // not in an object + throw new IllegalStateException("Didn't expect this to be named"); + } + + this.replaceTopState(DANGLING_NAME); + } + + private void prepareValue() throws IOException { + SonState state = this.peekState(); + switch(state) { + case EMPTY_DOCUMENT: + this.replaceTopState(NONEMPTY_DOCUMENT); + break; + case EMPTY_ARRAY: + this.replaceTopState(NONEMPTY_ARRAY); + break; + case NONEMPTY_ARRAY: + case NONEMPTY_OBJECT: + this.nextChar(true); // remove , from previous element + break; + case DANGLING_NAME: + this.replaceTopState(NONEMPTY_OBJECT); + break; + default: + throw new IllegalStateException(); + } + } + + private String readValue() throws IOException { + this.prepareValue(); + final StringBuilder value = new StringBuilder(); + int c = this.nextNonWhitespace(); + do { + value.append((char) c); + } while((c = this.eofRead()) != ',' && c != '}' && c != ']'); + this.rememberChar(c); + return value.toString().trim(); + } + + public void skipValue() throws IOException { + this.prepareValue(); + int count = 0; + int c; + do { + c = this.nextNonWhitespace(); + if(c == '{') { + this.pushState(EMPTY_OBJECT); + count++; + } else if(c == '[') { + this.pushState(EMPTY_ARRAY); + count++; + } else if(c == '}') { + this.popState(); + count--; + } else if(c == ']') { + this.popState(); + count--; + } else { + this.nextValue(); + } + } while(count != 0); + } + + @Override + public void close() throws IOException { + this.in.close(); + this.stackTop = 1; + this.stack[0] = CLOSED; + } + + /* character stream management */ + + private int nextNonWhitespace() throws IOException { + int read; + do { + read = this.nextChar(true); + } while(read == ' ' || read == '\t' || read == '\n'); + return read; + } + + private int nextChar(boolean remembered) throws IOException { + if(this.rememberedChar != null) { + if(remembered) { + int top = this.rememberedChar; + this.rememberedChar = null; + return top; + } else { + this.rememberedChar = null; + } + } + return eofRead(); + } + + private int eofRead() throws IOException { + int read = this.in.read(); + if(read == -1) { + throw new EOFException(); + } + return read; + } + + private void rememberChar(int c) { + if(this.rememberedChar != null) { + throw new IllegalStateException("Already remembering a char"); + } + this.rememberedChar = c; + } + + /* state management */ + + private SonState peekState() { + return this.stack[this.stackTop - 1]; + } + + private SonState popState() { + return this.stack[--this.stackTop]; + } + + private void pushState(SonState newTop) { + if(this.stackTop >= this.stack.length) { + this.stack = Arrays.copyOf(this.stack, this.stackTop * 2); + } + this.stack[this.stackTop++] = newTop; + } + + private void replaceTopState(SonState newTop) { + this.stack[this.stackTop - 1] = newTop; + } +} diff --git a/src/main/java/_8/io/SonState.java b/src/main/java/_8/io/SonState.java index 7178895..2281c99 100644 --- a/src/main/java/_8/io/SonState.java +++ b/src/main/java/_8/io/SonState.java @@ -7,5 +7,6 @@ public enum SonState { NONEMPTY_ARRAY, EMPTY_OBJECT, NONEMPTY_OBJECT, - DANGLING_NAME + DANGLING_NAME, + CLOSED } \ No newline at end of file