diff --git a/dormitory/src/main/java/ru/mifi/practice/ui/Model.java b/dormitory/src/main/java/ru/mifi/practice/ui/Model.java index e1bfdd4..41e40f4 100644 --- a/dormitory/src/main/java/ru/mifi/practice/ui/Model.java +++ b/dormitory/src/main/java/ru/mifi/practice/ui/Model.java @@ -47,7 +47,7 @@ final class Default extends Canvas implements Runnable, Model { private Font font; private int tickCount = 0; private int gameTime = 0; - private int lightning = 3; + private int lightning = 2; private int playerDeadTime; private int wonTimer = 0; private boolean hasWon = false; @@ -56,7 +56,7 @@ final class Default extends Canvas implements Runnable, Model { private int drawableTicks = 0; private int distance; private boolean fastQuit = false; - private Room room; + private final Room room; private Default(boolean development) { selfUpdate(); diff --git a/vol3/src/main/java/ru/mifi/practice/val3/hasq/Chain.java b/vol3/src/main/java/ru/mifi/practice/val3/hasq/Chain.java index 1f6bd23..55774f8 100644 --- a/vol3/src/main/java/ru/mifi/practice/val3/hasq/Chain.java +++ b/vol3/src/main/java/ru/mifi/practice/val3/hasq/Chain.java @@ -1,162 +1,136 @@ package ru.mifi.practice.val3.hasq; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.security.MessageDigest; -import java.util.ArrayList; -import java.util.HexFormat; -import java.util.List; -import java.util.Locale; -import java.util.Objects; - public interface Chain { + static Chain newChain(Token token, String passphrase) { + return new Default(token, passphrase); + } - public static void main(String[] args) throws IOException { - newChain(new Token("1000000000"), "init") - .add("init", "Next") - .add("init", "Next") - .add("init", "Next") - .write(Path.of("1000000000.csv")); + static Chain newChain(Record root) { + return new Default(root); } - static Chain newChain(Token token, String passphrase) { - return new Default(token).add(passphrase, "Created"); + default Chain add(String passphrase, String note) { + Key key = nextKey(passphrase); + return add(key, note); } - Chain write(Path path) throws IOException; + Chain add(Key key, String note); - Chain add(String passphrase, String note); + Record root(); + + Key nextKey(String passphrase); + + Result validate(); + + enum ValidateType { + SUCCESS, + FAILURE, + NOT_STARTED, + NOT_COMPLETE + } interface Value { String value(); } + record Detailed(Record record) { + + } + final class Default implements Chain { - private final List lines = new ArrayList<>(); + private final Record root; private final Token token; + private Record current; - Default(Token token) { + Default(Token token, String passphrase) { this.token = token; + this.root = Record.root(token, passphrase); + this.current = root; } - @Override - public Chain add(String passphrase, String note) { - if (lines.isEmpty()) { - Key key = createKey(0, passphrase); - lines.add(new Record(0, token, key, note)); - } else { - Record line = lines.get(lines.size() - 1); - int id = line.id + 1; - Key key = createKey(id, passphrase); - Generator generator = createGenerator(line.id, key); - line.generator = generator; - lines.add(new Record(id, token, key, note)); - if (lines.size() > 2) { - line = lines.get(lines.size() - 3); - line.owner = createOwner(line.id, generator); - } - } - return this; - } - - public Chain write(Path path) throws IOException { - Files.write(path, lines.stream().map(Record::toString).toList(), StandardOpenOption.CREATE_NEW); - return this; + Default(Record root) { + this.root = root; + this.token = root.token(); + this.current = root; } - public void read(Path path) throws IOException { - Files.readAllLines(path).forEach(line -> { - String[] parts = line.split(";"); - lines.add(Record.from(parts)); - }); + @Override + public Record root() { + return root; } - private Key createKey(int id, String passphrase) { - return new Key(id, Utils.hash(String.valueOf(id), token.value(), passphrase)); + @Override + public Chain add(Key key, String note) { + Generator generator = key.generator(current.id); + current.setGenerator(generator); + current = current.newRecord(key, note); + return this; } - private Generator createGenerator(int id, Key key) { - return new Generator(id, Utils.hash(String.valueOf(id), token.value(), key.value())); + @Override + public Key nextKey(String passphrase) { + return token.key(current.id + 1, passphrase); } - private Owner createOwner(int id, Generator generator) { - return new Owner(id, Utils.hash(String.valueOf(id), token.value(), generator.value())); + @Override + public Result validate() { + if (root == null) { + return Result.ok(ValidateType.NOT_STARTED); + } + try { + return root.validate(); + } catch (Exception ex) { + return Result.failure(null, ex.getMessage()); + } } } - final class Record { - private final int id; - private final Token token; - private final Key key; - private final String note; - private Generator generator; - private Owner owner; - - private Record(int id, Token token, Key key, String note) { - this(id, token, key, note, null, null); + record Key(int id, Token token, String value) implements Value, Hash { + @Override + public String toString() { + return value; } - public Record(int id, Token token, Key key, String note, Generator generator, Owner owner) { - this.id = id; - this.token = token; - this.key = key; - this.note = note; - this.generator = generator; - this.owner = owner; - } - - private static Record from(String[] parts) { - int id = Integer.parseInt(parts[0]); - if (parts.length == 6) { - return new Record( - id, - new Token(parts[1]), - new Key(id, parts[2]), - parts[5], - new Generator(id, parts[3]), - new Owner(id, parts[4]) - ); - } else { - return new Record( - id, - new Token(parts[1]), - new Key(id, parts[2]), - parts[5] - ); - } + public Generator generator(int id) { + return new Generator(id, this, hash(String.valueOf(this.id), token.value, value)); } @Override - public String toString() { - return String.format("%05d;%s;%s;%s;%s;%s", - id, token.value(), key.value(), - Objects.requireNonNullElse(generator, ""), - Objects.requireNonNullElse(owner, ""), - note); + public String hash(Object... args) { + return token.hash(args); } } - record Key(int id, String value) implements Value { + record Generator(int id, Key key, String value) implements Value, Hash { @Override public String toString() { return value; } - } - record Generator(int id, String value) implements Value { + public Owner owner(int id) { + return new Owner(id, hash(String.valueOf(key.id), key.token.value, key.value, value)); + } + @Override - public String toString() { - return value; + public String hash(Object... args) { + return key.hash(args); } } - record Token(String value) implements Value { + record Token(String value, Hash hash) implements Value, Hash { @Override public String toString() { return value; } + + public Key key(int n, String passphrase) { + return new Key(n, this, hash(String.valueOf(n), value(), passphrase)); + } + + @Override + public String hash(Object... args) { + return hash.hash(args); + } } record Owner(int id, String value) implements Value { @@ -165,22 +139,4 @@ public String toString() { return value; } } - - final class Utils { - private Utils() { - } - - private static String hash(Object... objects) { - try { - MessageDigest digest = MessageDigest.getInstance("MD5"); - for (Object object : objects) { - digest.update(object.toString().getBytes()); - } - byte[] bytes = digest.digest(); - return HexFormat.of().formatHex(bytes).toUpperCase(Locale.ROOT); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } } diff --git a/vol3/src/main/java/ru/mifi/practice/val3/hasq/ChainFile.java b/vol3/src/main/java/ru/mifi/practice/val3/hasq/ChainFile.java new file mode 100644 index 0000000..7f0da14 --- /dev/null +++ b/vol3/src/main/java/ru/mifi/practice/val3/hasq/ChainFile.java @@ -0,0 +1,36 @@ +package ru.mifi.practice.val3.hasq; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; + +public abstract class ChainFile { + static void write(Path path, Record root) throws IOException { + List records = new ArrayList<>(); + Record.createList(root, records); + if (Files.exists(path)) { + Files.delete(path); + } + Files.write(path, records.stream().map(Record::toString).toList(), StandardOpenOption.CREATE_NEW); + } + + static Chain read(Path path, Hash hash) throws IOException { + Record current = null; + Record root = null; + for (String line : Files.readAllLines(path)) { + String[] parts = line.split(";"); + Record prev = current; + current = Record.from(current, parts, hash); + if (root == null) { + root = current; + } + if (prev != null) { + prev.setNext(current); + } + } + return Chain.newChain(root); + } +} diff --git a/vol3/src/main/java/ru/mifi/practice/val3/hasq/Hash.java b/vol3/src/main/java/ru/mifi/practice/val3/hasq/Hash.java index f422004..bb2c622 100644 --- a/vol3/src/main/java/ru/mifi/practice/val3/hasq/Hash.java +++ b/vol3/src/main/java/ru/mifi/practice/val3/hasq/Hash.java @@ -1,5 +1,38 @@ package ru.mifi.practice.val3.hasq; +import java.security.MessageDigest; +import java.util.HexFormat; +import java.util.Locale; + public interface Hash { - String hash(); + Hash MD5 = new MessageDigestHash("MD5"); + Hash SHA1 = new MessageDigestHash("SHA1"); + Hash SHA256 = new MessageDigestHash("SHA256"); + Hash SHA512 = new MessageDigestHash("SHA512"); + Hash DEFAULT = SHA1; + + String hash(Object... args); + + final class MessageDigestHash implements Hash { + private final String algorithm; + + private MessageDigestHash(String algorithm) { + this.algorithm = algorithm; + } + + @Override + public String hash(Object... objects) { + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + for (Object object : objects) { + digest.update(object.toString().getBytes()); + } + byte[] bytes = digest.digest(); + return HexFormat.of().formatHex(bytes).toUpperCase(Locale.ROOT); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + } } diff --git a/vol3/src/main/java/ru/mifi/practice/val3/hasq/Main.java b/vol3/src/main/java/ru/mifi/practice/val3/hasq/Main.java new file mode 100644 index 0000000..19581da --- /dev/null +++ b/vol3/src/main/java/ru/mifi/practice/val3/hasq/Main.java @@ -0,0 +1,30 @@ +package ru.mifi.practice.val3.hasq; + +import java.io.IOException; +import java.nio.file.Path; + +import static ru.mifi.practice.val3.hasq.Chain.Detailed; +import static ru.mifi.practice.val3.hasq.Chain.Token; +import static ru.mifi.practice.val3.hasq.Chain.ValidateType; +import static ru.mifi.practice.val3.hasq.Chain.newChain; + +public abstract class Main { + public static void main(String[] args) throws IOException { + String tokenValue = "1000000000"; + Path filePath = Path.of(tokenValue + ".csv"); + Hash hash = Hash.DEFAULT; + Chain chain = newChain(new Token(tokenValue, hash), "init") + .add("init", "N1") + .add("init", "N2") + .add("init", "N3") + .add("init", "N4") + .add("init", "N5") + .add("init", "N6"); + ChainFile.write(filePath, chain.root()); + Chain read = ChainFile.read(filePath, hash); + Result validated = read.validate(); + System.out.println(validated); + validated = chain.validate(); + System.out.println(validated); + } +} diff --git a/vol3/src/main/java/ru/mifi/practice/val3/hasq/Record.java b/vol3/src/main/java/ru/mifi/practice/val3/hasq/Record.java new file mode 100644 index 0000000..8b88821 --- /dev/null +++ b/vol3/src/main/java/ru/mifi/practice/val3/hasq/Record.java @@ -0,0 +1,116 @@ +package ru.mifi.practice.val3.hasq; + +import java.util.List; +import java.util.Objects; + +import static ru.mifi.practice.val3.hasq.Chain.Detailed; +import static ru.mifi.practice.val3.hasq.Chain.Generator; +import static ru.mifi.practice.val3.hasq.Chain.Key; +import static ru.mifi.practice.val3.hasq.Chain.Owner; +import static ru.mifi.practice.val3.hasq.Chain.Token; +import static ru.mifi.practice.val3.hasq.Chain.ValidateType; + +public final class Record { + final int id; + private final Record parent; + private final Token token; + private final Key key; + private final String note; + private Record next; + private Generator generator; + private Owner owner; + + private Record(Record parent, int id, Token token, Key key, String note) { + this(parent, id, token, key, note, null, null); + } + + private Record(Record parent, int id, Token token, Key key, String note, Generator generator, Owner owner) { + this.parent = parent; + this.id = id; + this.token = token; + this.key = key; + this.note = note; + this.generator = generator; + this.owner = owner; + } + + static Record root(Token token, String passphrase) { + return new Record(null, 0, token, token.key(0, passphrase), "NE"); + } + + static Record from(Record parent, String[] parts, Hash hash) { + int id = Integer.parseInt(parts[0].trim()); + Token token = new Token(parts[1].trim(), hash); + Key key = new Key(id, token, parts[2].trim()); + if (parts.length == 6) { + String generator = parts[3].trim(); + String owner = parts[4].trim(); + + return new Record( + parent, id, token, key, parts[5].trim(), + generator.isEmpty() ? null : new Generator(id, key, parts[3]), + owner.isEmpty() ? null : new Owner(id, parts[4]) + ); + } else { + return new Record(parent, id, token, key, parts[5].trim()); + } + } + + static void createList(Record root, List children) { + children.add(root); + if (root.next != null) { + createList(root.next, children); + } + } + + Token token() { + return token; + } + + void setGenerator(Generator generator) { + this.generator = generator; + if (parent != null) { + parent.owner = generator.owner(parent.id); + } + } + + Record newRecord(Key key, String note) { + next = new Record(this, key.id(), token, key, note, null, null); + return next; + } + + @Override + public String toString() { + return String.format("%05d;%s;%s;%s;%s;%s", + id, token.value(), key.value(), + Objects.requireNonNullElse(generator, ""), Objects.requireNonNullElse(owner, ""), note); + } + + void setNext(Record next) { + this.next = next; + } + + public Result validate() { + if (generator == null) { + if (next == null) { + return Result.ok(ValidateType.SUCCESS); + } + return Result.ok(ValidateType.NOT_COMPLETE); + } + if (next == null) { + return Result.failure(new Detailed(this), "Next record is null"); + } + String hash = token.hash(String.valueOf(next.id), token.value(), next.key.value()); + if (!hash.equals(generator.value())) { + return Result.failure(new Detailed(next), "Hash for generator does not match"); + } + if (owner == null) { + return Result.ok(ValidateType.SUCCESS); + } + hash = token.hash(String.valueOf(next.next.id), token.value(), next.next.key.value(), next.generator.value()); + if (!hash.equals(owner.value())) { + return Result.failure(new Detailed(next.next), "Hash for owner does not match"); + } + return next.validate(); + } +} diff --git a/vol3/src/main/java/ru/mifi/practice/val3/hasq/Result.java b/vol3/src/main/java/ru/mifi/practice/val3/hasq/Result.java new file mode 100644 index 0000000..1703c4d --- /dev/null +++ b/vol3/src/main/java/ru/mifi/practice/val3/hasq/Result.java @@ -0,0 +1,56 @@ +package ru.mifi.practice.val3.hasq; + +public sealed interface Result { + + static Result ok(R result) { + return new Body<>(result, null, null); + } + + static Result failure(E error, String message) { + return new Body<>(null, error, message); + } + + R getOr(R result); + + Error error(); + + final class Error { + final B value; + final String error; + + private Error(B value, String error) { + this.value = value; + this.error = error; + } + + @Override + public String toString() { + return value + "(" + error + ")"; + } + } + + final class Body implements Result { + private final R result; + private final Error error; + + private Body(R result, E error, String message) { + this.result = result; + this.error = new Error<>(error, message); + } + + @Override + public R getOr(R result) { + return this.result == null ? result : this.result; + } + + @Override + public Error error() { + return error; + } + + @Override + public String toString() { + return result == null ? error.toString() : result.toString(); + } + } +}