diff --git a/pom.xml b/pom.xml index 4e600a4..a4f12af 100644 --- a/pom.xml +++ b/pom.xml @@ -9,6 +9,7 @@ UTF-8 1.8 1.8 + 5.0.2 @@ -23,31 +24,46 @@ - + + maven-compiler-plugin + 3.7.0 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + + org.junit.platform + junit-platform-surefire-provider + 1.0.2 + + + - - org.apache.commons - commons-csv - 1.5 - + + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} + test + + + org.junit.platform + junit-platform-launcher + 1.0.2 + test + \ No newline at end of file diff --git a/src/main/java/jp/co/training/Book.java b/src/main/java/jp/co/training/Book.java deleted file mode 100644 index ad5515a..0000000 --- a/src/main/java/jp/co/training/Book.java +++ /dev/null @@ -1,111 +0,0 @@ -package jp.co.training; - -import static jp.co.training.Const.MAX_AUTHOR; -import static jp.co.training.Const.MAX_BOOK_NAME; -import static jp.co.training.Const.MAX_ISBN; -import static jp.co.training.Const.MAX_PRICE; -import static jp.co.training.Const.MAX_PUBLISHER; - -public class Book { - - private final String isbn; - private final String bookName; - private final String author; - private final String publisher; - private final String publicationDate; - private final String price; - - - public Result validate() { - Result result = new Result(); - - //最大最小チェック - BookUtil.checkRange(isbn, 1, MAX_ISBN, "ISBM").ifPresent(msg -> result.addErrMessage(msg)); - BookUtil.checkRange(bookName, 1, MAX_BOOK_NAME, "BOOK_NAME").ifPresent(msg -> result.addErrMessage(msg)); - BookUtil.checkRange(author, 1, MAX_AUTHOR, "AUTHOR").ifPresent(msg -> result.addErrMessage(msg)); - BookUtil.checkRange(publisher, 1, MAX_PUBLISHER, "PUBLISHER").ifPresent(msg -> result.addErrMessage(msg)); - BookUtil.checkRange(price, 1, MAX_PRICE, "PRICE").ifPresent(msg -> result.addErrMessage(msg)); - - - - return result; - } - - public String getIsbn() { - return isbn; - } - - public String getBookName() { - return bookName; - } - - public String getAuthor() { - return author; - } - - public String getPublisher() { - return publisher; - } - - public String getPublicationDate() { - return publicationDate; - } - - public String getPrice() { - return price; - } - - private Book(Builder builder) { - this.isbn = builder.isbn; - this.bookName = builder.bookName; - this.author = builder.author; - this.publisher = builder.publisher; - this.publicationDate = builder.publicationDate; - this.price = builder.price; - } - - public static class Builder { - private String isbn; - private String bookName; - private String author; - private String publisher; - private String publicationDate; - private String price; - - public Builder isbn(String isbn) { - this.isbn = isbn; - return this; - } - - public Builder bookName(String bookName) { - this.bookName = bookName; - return this; - } - - public Builder author(String author) { - this.author = author; - return this; - } - - public Builder publisher(String publisher) { - this.publisher = publisher; - return this; - } - - public Builder publicationDate(String publicationDate) { - this.publicationDate = publicationDate; - return this; - } - - public Builder price(String price) { - this.price = price; - return this; - } - - public Book build() { - return new Book(this); - } - - } - -} diff --git a/src/main/java/jp/co/training/BookUtil.java b/src/main/java/jp/co/training/BookUtil.java deleted file mode 100644 index 19e52b7..0000000 --- a/src/main/java/jp/co/training/BookUtil.java +++ /dev/null @@ -1,29 +0,0 @@ -package jp.co.training; - -import java.util.Optional; - -public class BookUtil { - - private BookUtil() { - } - - /** - * 対象文字列が指定された最小文字数と最大文字数の範囲内であるかをチェックします
- * 範囲内である場合はOptionalの中身はnullとなる
- * 範囲外である場合はOptionalの中身は範囲外であることを示すメッセージとなる
- * - * @param target : - * @param min - * @param max - * @param targetName - * @return - */ - public static Optional checkRange(String target, int min, int max, String targetName) { - if (target.length() < min) { - return Optional.of(targetName + "of size: min is " + min + ". but your input is " + target.length() + "."); - } else if (target.length() > max) { - return Optional.of(targetName + "of size: max is " + max + ". but your input is " + target.length() + "."); - } - return Optional.empty(); - } -} diff --git a/src/main/java/jp/co/training/Command.java b/src/main/java/jp/co/training/Command.java deleted file mode 100644 index 538072e..0000000 --- a/src/main/java/jp/co/training/Command.java +++ /dev/null @@ -1,12 +0,0 @@ -package jp.co.training; - -import java.util.List; - -public interface Command { - - void setArgments(List argments); - - void execute(); - - Result validate(); -} diff --git a/src/main/java/jp/co/training/CommandFactory.java b/src/main/java/jp/co/training/CommandFactory.java deleted file mode 100644 index 3010dc0..0000000 --- a/src/main/java/jp/co/training/CommandFactory.java +++ /dev/null @@ -1,19 +0,0 @@ -package jp.co.training; - -import static jp.co.training.Const.EXIT; -import static jp.co.training.Const.INSERT; - -public final class CommandFactory { - - public static Command createCommand(String command) { - - switch (command) { - case INSERT: - return new InsertCommand(); - case EXIT: - return new ExitCommand(); - default: - return new InvalidCommand(); - } - } -} diff --git a/src/main/java/jp/co/training/Config.java b/src/main/java/jp/co/training/Config.java new file mode 100644 index 0000000..1e80178 --- /dev/null +++ b/src/main/java/jp/co/training/Config.java @@ -0,0 +1,61 @@ +package jp.co.training; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import jp.co.training.book.ItemCode; + +public class Config { + + // 外部ファイルによる設定が可能な定数 + public String delimiter; + public String saveFile; + public String userName; + + private static final String DELIMITER_KEY = "delimiter"; + private static final String SAVE_FILE_KEY = "save.file"; + private static final String USER_NAME_KEY = "user.name"; + + ResourceBundle rbMessages; + + //メッセージ定義ファイルのパス + private static final String MESSAGES_RESOURCE = "messages"; + + public Config load(String path) { + ResourceBundle rbProperties = null; + try { + rbProperties = ResourceBundle.getBundle(path); + } catch (MissingResourceException e) { + System.out.println("ERROR: cannnot find configfile."); + System.exit(1); + } + if (!rbProperties.containsKey(USER_NAME_KEY)) { + System.out.println("ERROR: you must define registrant in configfile."); + System.exit(1); + } + + try { + rbMessages = ResourceBundle.getBundle(MESSAGES_RESOURCE); + } catch (MissingResourceException e) { + System.out.println("ERROR: cannnot find message file."); + System.exit(1); + } + + userName = rbProperties.getString(USER_NAME_KEY); + delimiter = (rbProperties.containsKey(DELIMITER_KEY)) ? rbProperties.getString(DELIMITER_KEY) : ","; + saveFile = (rbProperties.containsKey(SAVE_FILE_KEY)) ? rbProperties.getString(DELIMITER_KEY) : "savefile"; + return this; + } + + public String getMessage(ItemCode code, String... params) { + + // メッセージ取得 + String msg = rbMessages.getString(code.getCode()); + + // 可変項目の置換え + for (int i = 0; i < params.length; i++) { + msg.replaceFirst("{" + i + "}", params[i]); + } + return msg; + } +} diff --git a/src/main/java/jp/co/training/Const.java b/src/main/java/jp/co/training/Const.java index a4a3ab8..cb5b0f6 100644 --- a/src/main/java/jp/co/training/Const.java +++ b/src/main/java/jp/co/training/Const.java @@ -1,43 +1,22 @@ package jp.co.training; -import java.util.MissingResourceException; -import java.util.ResourceBundle; - public final class Const { - //外部ファイルによる設定不可能な定数 + public static final String CONFIG_FILE = "resource"; public static final String INSERT = "insert"; + public static final String UPDATE = "update"; + public static final String SET = "set"; + public static final String SELECT = "select"; + public static final String ORDERBY = "orderby"; public static final String EXIT = "exit"; + public static final String INVALID = "invalid"; public static final int MAX_ISBN = 13; public static final int MAX_BOOK_NAME = 50; public static final int MAX_AUTHOR = 30; public static final int MAX_PUBLISHER = 30; public static final int MAX_PRICE = 9; + public static final String DATE_PATTERN = "yyyyMMdd"; + public static final String DATE_TIME_PATTERN = "yyyyMMdd hhmmss.SSS"; + public static final int ID_LENGTH = 16; - //外部ファイルによる設定が可能な定数 - public static final String PROMPT; - public static final String DELIMITER; - public static final String SAVE_FILE; - - //外部ファイルの設定ファイルのキー - private static final String PROMPT_KEY = "prompt"; - private static final String DELIMITER_KEY = "delimiter"; - private static final String SAVE_FILE_KEY = "save.file"; - - static { - ResourceBundle rb = loadResource(); - PROMPT = (rb == null || !rb.containsKey(PROMPT_KEY)) ? "books>" : rb.getString(PROMPT_KEY); - DELIMITER = (rb == null || !rb.containsKey(DELIMITER_KEY)) ? "," : rb.getString(DELIMITER_KEY); - SAVE_FILE = (rb == null || !rb.containsKey(SAVE_FILE_KEY)) ? "savefile" : rb.getString(SAVE_FILE_KEY); - } - - private static ResourceBundle loadResource() { - ResourceBundle rb = null; - try { - rb = ResourceBundle.getBundle("resource"); - } catch (MissingResourceException e) { - System.out.println("WARNNING: cannot find resource.properties"); - } - return rb; - } } diff --git a/src/main/java/jp/co/training/ExitCommand.java b/src/main/java/jp/co/training/ExitCommand.java deleted file mode 100644 index 8487f8f..0000000 --- a/src/main/java/jp/co/training/ExitCommand.java +++ /dev/null @@ -1,31 +0,0 @@ -package jp.co.training; - -import java.util.List; - -public final class ExitCommand implements Command { - - private List argments; - @Override - public void execute() { - System.exit(0); - } - - @Override - public void setArgments(List argments) { - this.argments = argments; - } - - @Override - public Result validate() { - Result result = new Result(); - //パラメータ数 - if (argments != null && !argments.isEmpty()) { - if (argments.size() == 1 && argments.get(0).trim().equals("")) { - return result; - } - result.addErrMessage("SyntaxError. The number of arguments does not match."); - } - return result; - } - -} diff --git a/src/main/java/jp/co/training/InsertCommand.java b/src/main/java/jp/co/training/InsertCommand.java deleted file mode 100644 index 80e02fe..0000000 --- a/src/main/java/jp/co/training/InsertCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -package jp.co.training; - -import java.io.BufferedWriter; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import static jp.co.training.Const.SAVE_FILE; - -public final class InsertCommand implements Command { - - private List argments; - - private Book book; - - @Override - public void setArgments(List argments) { - this.argments = argments; - } - - @Override - public void execute() { - try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(SAVE_FILE, true), StandardCharsets.UTF_8))) { - for (int i = 0, size = argments.size(); i < size; i++) { - bw.write((i == 0 ? "" : ",") + argments.get(i).trim()); - } - bw.newLine(); - } catch (IOException ex) { - Logger.getLogger(InsertCommand.class.getName()).log(Level.SEVERE, null, ex); - } - System.out.println("inserted."); - } - - @Override - public Result validate() { - Result result = new Result(); - //パラメータ数 - if (argments.size() != 6) { - result.addErrMessage("SyntaxError. The number of arguments does not match."); - } - book = new Book.Builder().isbn(argments.get(0)) - .bookName(argments.get(1)) - .author(argments.get(2)) - .publisher(argments.get(3)) - .publicationDate(argments.get(4)) - .author(argments.get(5)) - .price(argments.get(6)) - .build(); - result.getErrMesages().addAll(book.validate().getErrMesages()); - - return result; - } - -} diff --git a/src/main/java/jp/co/training/InvalidCommand.java b/src/main/java/jp/co/training/InvalidCommand.java deleted file mode 100644 index f5063ba..0000000 --- a/src/main/java/jp/co/training/InvalidCommand.java +++ /dev/null @@ -1,22 +0,0 @@ -package jp.co.training; - -import java.util.List; - -public final class InvalidCommand implements Command { - - @Override - public void setArgments(List argments) { - } - - @Override - public void execute() { - } - - @Override - public Result validate() { - Result result = new Result(); - result.addErrMessage("Invalid Command."); - return result; - } - -} diff --git a/src/main/java/jp/co/training/Main.java b/src/main/java/jp/co/training/Main.java index 8bca169..9cd8cc4 100644 --- a/src/main/java/jp/co/training/Main.java +++ b/src/main/java/jp/co/training/Main.java @@ -1,31 +1,46 @@ package jp.co.training; +import static jp.co.training.Const.*; + import java.io.FileNotFoundException; import java.io.IOException; -import java.util.Arrays; -import java.util.List; import java.util.Scanner; -import static jp.co.training.Const.DELIMITER; -import static jp.co.training.Const.PROMPT; + +import jp.co.training.command.Command; +import jp.co.training.command.CommandResult; +import jp.co.training.command.ExitCommand; +import jp.co.training.command.InsertCommand; public final class Main { + public static Config config; + public static void main(String... args) throws FileNotFoundException, IOException { + + config = new Config().load(args.length > 0 && args[0] != null ? args[0] : CONFIG_FILE); + Viewer viewer = new Viewer(); + + Command command = new ExitCommand(EXIT); + command.setNext(new InsertCommand(INSERT)); + try (Scanner scan = new Scanner(System.in)) { while (true) { - System.out.print(PROMPT); - Command command = CommandFactory.createCommand(scan.next()); - List argments = Arrays.asList(scan.nextLine().split(DELIMITER)); - - command.setArgments(argments); - Result result = command.validate(); - if (result != null && result.getErrMesages().size() > 0) { - result.getErrMesages().stream().forEach(message -> { - System.err.println(message); - }); + viewer.promptMessages(); + String inputCommand = scan.next().toLowerCase(); + String argments = scan.nextLine().trim(); + + // コマンド実行 + CommandResult result = command.execute(inputCommand, argments); + + //実行結果を出力 + viewer.commandMessages(result); + + if (result != null && result.isExit()) { + break; } - command.execute(); } } + viewer.endMessages(); } + } diff --git a/src/main/java/jp/co/training/Result.java b/src/main/java/jp/co/training/Result.java deleted file mode 100644 index 6e0a6e2..0000000 --- a/src/main/java/jp/co/training/Result.java +++ /dev/null @@ -1,17 +0,0 @@ -package jp.co.training; - -import java.util.ArrayList; -import java.util.List; - -public class Result { - - private final List errMesages = new ArrayList<>(); - - public List getErrMesages() { - return errMesages; - } - - public void addErrMessage(String msg) { - errMesages.add(msg); - } -} diff --git a/src/main/java/jp/co/training/Viewer.java b/src/main/java/jp/co/training/Viewer.java new file mode 100644 index 0000000..9491c55 --- /dev/null +++ b/src/main/java/jp/co/training/Viewer.java @@ -0,0 +1,77 @@ +package jp.co.training; + +import static jp.co.training.Const.*; + +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; + +import jp.co.training.book.ItemCode; +import jp.co.training.command.CommandResult; +import jp.co.training.common.Code; +import jp.co.training.common.Status; + +public class Viewer { + + private ResourceBundle rbMessages; + + // メッセージ定義ファイルのキー + private static final String PROMPT_KEY = "prompt"; + private static final String END_MESSAGE_KEY = "end.message"; + + //メッセージ定義ファイルのパス + private static final String MESSAGES_RESOURCE = "messages"; + + public Viewer() { + try { + rbMessages = ResourceBundle.getBundle(MESSAGES_RESOURCE); + } catch (MissingResourceException e) { + System.out.println("ERROR: cannnot find message file."); + System.exit(1); + } + } + + public void promptMessages() { + System.out.print(rbMessages.containsKey(PROMPT_KEY) ? rbMessages.getString(PROMPT_KEY) : "books>"); + } + + public void endMessages() { + System.out.print(rbMessages.containsKey(END_MESSAGE_KEY) ? rbMessages.getString(PROMPT_KEY) : "bye."); + } + + public void commandMessages(CommandResult commandResult) { + if (commandResult.getStatus() == Status.OK) { + standardMessages(commandResult.getCommandName()); + } else { + errorMessages(commandResult.getCodes()); + errorMessages(commandResult.getItemCodes()); + } + + } + + public void standardMessages(String commandName) { + switch (commandName) { + case INSERT: + System.out.println("inserted"); + break; + default: + } + } + + public void errorMessages(Map> itemCodes) { + for (Map.Entry> entry : itemCodes.entrySet()) { + System.out.println(entry.getKey()); + for (ItemCode code : entry.getValue()) { + System.out.println(code); + } + } + } + + public void errorMessages(Set codes) { + for (Code code : codes) { + System.out.println(code); + } + } + +} diff --git a/src/main/java/jp/co/training/book/Book.java b/src/main/java/jp/co/training/book/Book.java new file mode 100644 index 0000000..287ad12 --- /dev/null +++ b/src/main/java/jp/co/training/book/Book.java @@ -0,0 +1,246 @@ +package jp.co.training.book; + +import static jp.co.training.Const.*; +import static jp.co.training.Main.*; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +import jp.co.training.common.Entity; +import jp.co.training.common.Status; +import jp.co.training.util.BookUtil; +import jp.co.training.validate.ItemValidater; + +public class Book extends Entity { + + public static final int NUMBER_OF_ITEMS = 6; + + private String id; + private String isbn; + private String bookName; + private String author; + private String publisher; + private String publicationDate; + private String price; + + public String getIsbn() { + return isbn; + } + + public String getBookName() { + return bookName; + } + + public String getAuthor() { + return author; + } + + public String getPublisher() { + return publisher; + } + + public String getPublicationDate() { + return publicationDate; + } + + public String getPrice() { + return price; + } + + private Book(Builder builder) { + this.id = builder.id; + this.isbn = builder.isbn; + this.bookName = builder.bookName; + this.author = builder.author; + this.publisher = builder.publisher; + this.publicationDate = builder.publicationDate; + this.price = builder.price; + this.createUser = builder.createUser; + this.createdDate = builder.createdDate; + this.updateUser = builder.updateUser; + this.updatedDate = builder.updatedDate; + } + + public static final int NUM_ISBN = 0; + public static final int NUM_BOOK_NAME = 1; + public static final int NUM_AUTHOR = 2; + public static final int NUM_PUBLISHER = 3; + public static final int NUM_PRICE = 4; + public static final int NUM_PUBLICATION_DATE = 5; + + public static BookResult createBook(String[] argments) { + + BookResult result = new BookResult(); + + //各項目のバリデーション + + // isbn + ItemValidater.checkLength(argments[NUM_ISBN], 1, MAX_ISBN).ifPresent(code -> result.addCode("ISBM", code)); + + // bookName + ItemValidater.checkLength(argments[NUM_BOOK_NAME], 1, MAX_BOOK_NAME) + .ifPresent(code -> result.addCode("BOOK_NAME", code)); + + // author + ItemValidater.checkLength(argments[NUM_AUTHOR], 1, MAX_AUTHOR) + .ifPresent(code -> result.addCode("AUTHOR", code)); + + // publisher + ItemValidater.checkLength(argments[NUM_PUBLISHER], 1, MAX_PUBLISHER) + .ifPresent(code -> result.addCode("PUBLISHER", code)); + + // price + ItemValidater.checkLength(argments[NUM_PRICE], 1, MAX_PRICE) + .ifPresent(code -> result.addCode("PRICE", code)); + ItemValidater.checkNumber(argments[NUM_PRICE]).ifPresent(code -> result.addCode("PRICE", code)); + + // publicationDate + ItemValidater.checkDatePattern(argments[NUM_PUBLICATION_DATE], DATE_PATTERN) + .ifPresent(code -> result.addCode("PUBLICATION_DATE", code)); + + if (!result.getItemCodes().isEmpty()) { + return result; + } + + String today = LocalDateTime.now().format(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)); + + Book book = new Book.Builder() + .id(BookUtil.generateID(ID_LENGTH)) + .isbn(argments[NUM_ISBN]) + .bookName(argments[NUM_BOOK_NAME]) + .author(argments[NUM_AUTHOR]) + .publisher(argments[NUM_PUBLISHER]) + .publicationDate(argments[NUM_PUBLICATION_DATE]) + .price(argments[NUM_PRICE]) + .createUser(config.userName) + .createdDate(today) + .updateUser(config.userName) + .updatedDate(today) + .build(); + + result.setBook(book); + result.setStatus(Status.OK); + return result; + } + + public static class Builder { + + private String id; + private String isbn; + private String bookName; + private String author; + private String publisher; + private String publicationDate; + private String price; + private String createUser; + private String createdDate; + private String updateUser; + private String updatedDate; + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder isbn(String isbn) { + this.isbn = isbn; + return this; + } + + public Builder bookName(String bookName) { + this.bookName = bookName; + return this; + } + + public Builder author(String author) { + this.author = author; + return this; + } + + public Builder publisher(String publisher) { + this.publisher = publisher; + return this; + } + + public Builder publicationDate(String publicationDate) { + this.publicationDate = publicationDate; + return this; + } + + public Builder price(String price) { + this.price = price; + return this; + } + + public Builder createUser(String createUser) { + this.createUser = createUser; + return this; + } + + public Builder createdDate(String createdDate) { + this.createdDate = createdDate; + return this; + } + + public Builder updateUser(String updateUser) { + this.updateUser = updateUser; + return this; + } + + public Builder updatedDate(String updatedDate) { + this.updatedDate = updatedDate; + return this; + } + + public Book build() { + return new Book(this); + } + + } + + @Override + public String encode() { + return String.join(config.delimiter, + this.id, + this.isbn, + this.bookName, + this.author, + this.publisher, + this.publicationDate, + this.price, + this.createUser, + this.createdDate, + this.updateUser, + this.updatedDate); + } + + public static Book decode(String params) { + return null;//TODO Bookを返却するように修正 + } + + public List output(String[] cols) { + + List list = new ArrayList<>(); + //TODO 作成中 + for (int i = 0; i < cols.length; i++) { + switch (Book.BookCol.valueOf(cols[i])) { + case ID: + list.add(this.id); + break; + case ISBN: + list.add(this.isbn); + break; + default: + + } + } + + return list; + } + + public enum BookCol { + ID, ISBN, BOOK_NAME, AUTHOR, PUBLISHER, PUBLICATION_DATE, PRICE, CREATE_USER, CREATE_DATE, UPDATE_USER, UPDATE_DATE + } +} diff --git a/src/main/java/jp/co/training/book/BookResult.java b/src/main/java/jp/co/training/book/BookResult.java new file mode 100644 index 0000000..11a097d --- /dev/null +++ b/src/main/java/jp/co/training/book/BookResult.java @@ -0,0 +1,34 @@ +package jp.co.training.book; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import jp.co.training.common.Result; + +public class BookResult extends Result { + + private Book book; + + private Map> itemCodes; + + public Book getBook() { + return book; + } + + public void setBook(Book book) { + this.book = book; + } + + public Map> getItemCodes() { + return itemCodes; + } + + public void addCode(String key, ItemCode code) { + if (itemCodes.get(key) == null) { + itemCodes.put(key, new HashSet<>()); + } + itemCodes.get(key).add(code); + } + +} diff --git a/src/main/java/jp/co/training/book/ItemCode.java b/src/main/java/jp/co/training/book/ItemCode.java new file mode 100644 index 0000000..46358cd --- /dev/null +++ b/src/main/java/jp/co/training/book/ItemCode.java @@ -0,0 +1,26 @@ +package jp.co.training.book; + +import jp.co.training.common.Code; + +/** + * 項目ごとのコード + * + */ +public enum ItemCode implements Code { + + MAX_LENGTH_OVER("E101"), MIN_LENGTH_UNDER("E102"), + + INVALID_DATE_PATTERN("E103"), NOT_NUMERICAL("E104"); + + private final String code; + + ItemCode(String code) { + this.code = code; + } + + @Override + public String getCode() { + return code; + } + +} diff --git a/src/main/java/jp/co/training/command/Command.java b/src/main/java/jp/co/training/command/Command.java new file mode 100644 index 0000000..617cae1 --- /dev/null +++ b/src/main/java/jp/co/training/command/Command.java @@ -0,0 +1,34 @@ +package jp.co.training.command; + +import static jp.co.training.Const.*; + +public abstract class Command { + + protected String name; + + protected Command next; + + public Command(String name) { + this.name = name; + } + + public CommandResult execute(String command, String argments) { + if (command.equals(name)) { + return executeCommand(argments); + } else if (next != null) { + return next.execute(command, argments); + } + // どのコマンドにも合致しなかった場合 + CommandResult result = new CommandResult(INVALID); + result.addCode(CommandCode.INVALID_COMMAND); + return result; + } + + public Command setNext(Command next) { + this.next = next; + return next; + } + + public abstract CommandResult executeCommand(String argments); + +} diff --git a/src/main/java/jp/co/training/command/CommandCode.java b/src/main/java/jp/co/training/command/CommandCode.java new file mode 100644 index 0000000..5ebfa77 --- /dev/null +++ b/src/main/java/jp/co/training/command/CommandCode.java @@ -0,0 +1,19 @@ +package jp.co.training.command; + +import jp.co.training.common.Code; + +public enum CommandCode implements Code { + + WRONG_NUMBER_OF_ARGUMENTS("E001"), INVALID_COMMAND("E002"); + + private final String code; + + CommandCode(String code) { + this.code = code; + } + + @Override + public String getCode() { + return this.code; + } +} diff --git a/src/main/java/jp/co/training/command/CommandResult.java b/src/main/java/jp/co/training/command/CommandResult.java new file mode 100644 index 0000000..5898fb7 --- /dev/null +++ b/src/main/java/jp/co/training/command/CommandResult.java @@ -0,0 +1,80 @@ +package jp.co.training.command; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jp.co.training.book.ItemCode; +import jp.co.training.common.Code; +import jp.co.training.common.Result; + +public class CommandResult extends Result { + + private boolean exit = false; + + private final String commandName; + + private Map> itemCodes = new HashMap<>(); + + private Set codes = new HashSet<>(); + + private List> records; + + public List> getRecords() { + return records; + } + + public void addRecord(List record) { + this.records.add(record); + } + + public CommandResult(String commandName) { + this.commandName = commandName; + } + + public boolean isExit() { + return exit; + } + + public void setExit(boolean exit) { + this.exit = exit; + } + + public String getCommandName() { + return commandName; + } + + public Map> getItemCodes() { + return itemCodes; + } + + public void addItemCode(String key, ItemCode itemCode) { + if (itemCodes.get(key) == null) { + itemCodes.put(key, new HashSet<>()); + } + itemCodes.get(key).add(itemCode); + } + + public void addItemCodes(Map> itemCodes) { + for (Map.Entry> e : itemCodes.entrySet()) { + for (ItemCode code : e.getValue()) { + addItemCode(e.getKey(), code); + } + } + } + + public Set getCodes() { + return codes; + } + + public void addCode(Code code) { + codes.add(code); + } + + public void addCodes(Set codes) { + this.codes.addAll(codes); + } + +} diff --git a/src/main/java/jp/co/training/command/ExitCommand.java b/src/main/java/jp/co/training/command/ExitCommand.java new file mode 100644 index 0000000..4971abf --- /dev/null +++ b/src/main/java/jp/co/training/command/ExitCommand.java @@ -0,0 +1,20 @@ +package jp.co.training.command; + +import static jp.co.training.Const.*; + +public final class ExitCommand extends Command { + + public ExitCommand(String name) { + super(name); + } + + @Override + public CommandResult executeCommand(String argments) { + CommandResult result = new CommandResult(EXIT); + //TODO 以下の処理はviewerクラスに任せる + // result.addMessage(config.endMessage); + result.setExit(true); + return result; + } + +} diff --git a/src/main/java/jp/co/training/command/InsertCommand.java b/src/main/java/jp/co/training/command/InsertCommand.java new file mode 100644 index 0000000..e66a6c2 --- /dev/null +++ b/src/main/java/jp/co/training/command/InsertCommand.java @@ -0,0 +1,51 @@ +package jp.co.training.command; + +import static jp.co.training.Const.*; +import static jp.co.training.Main.*; +import static jp.co.training.command.CommandCode.*; + +import java.util.Arrays; + +import jp.co.training.book.Book; +import jp.co.training.book.BookResult; +import jp.co.training.common.Status; +import jp.co.training.dao.BookDao; +import jp.co.training.dao.DaoResult; + +public final class InsertCommand extends Command { + + public InsertCommand(String name) { + super(name); + } + + @Override + public CommandResult executeCommand(String argments) { + + CommandResult commandResult = new CommandResult(INSERT); + + //引数をinsert用にパース + String[] params = argments.split(config.delimiter); + params = (String[]) Arrays.stream(params).map(e -> e.trim()).toArray(String[]::new); + + // パラメータ数チェック + if (params.length != Book.NUMBER_OF_ITEMS) { + commandResult.addCode(WRONG_NUMBER_OF_ARGUMENTS); + return commandResult; + } + + //Bookオブジェクト生成 + BookResult bookResult = Book.createBook(params); + + if (bookResult.getStatus() == Status.NG) { + commandResult.addItemCodes(bookResult.getItemCodes()); + return commandResult; + } + + //書籍情報の登録 + DaoResult daoResult = new BookDao().insert(bookResult.getBook()); + commandResult.addCodes(daoResult.getCodes()); + commandResult.setStatus(Status.OK); + return commandResult; + } + +} diff --git a/src/main/java/jp/co/training/command/SelectCommand.java b/src/main/java/jp/co/training/command/SelectCommand.java new file mode 100644 index 0000000..b51d0f5 --- /dev/null +++ b/src/main/java/jp/co/training/command/SelectCommand.java @@ -0,0 +1,47 @@ +package jp.co.training.command; + +import static jp.co.training.Const.*; +import static jp.co.training.Main.*; + +import java.util.List; + +import jp.co.training.book.Book; +import jp.co.training.dao.BookDao; +import jp.co.training.dao.SelectDaoResult; + +public class SelectCommand extends Command { + + public SelectCommand(String name) { + super(name); + } + + @Override + public CommandResult executeCommand(String argments) { + + CommandResult result = new CommandResult(SELECT); + + //TODO argments解析 + //orderbyまでを取り出し、それ以前を,区切りで配列にする、それ以降を空白区切りで配列にする + + String[] cols; + String[] orderCols; + if (argments.toLowerCase().indexOf("orderby") == -1) { + cols = argments.split(config.delimiter); + } else { + cols = argments.substring(0, argments.toLowerCase().indexOf(ORDERBY)).split(config.delimiter); + orderCols = argments.substring(argments.toLowerCase().indexOf(ORDERBY) + 1).split(config.delimiter); + } + + //colsのバリデーション + + // + SelectDaoResult daoResult = new BookDao().select(); + List records = daoResult.getRecords(); + + for (Book book : records) { + result.addRecord((book.output(cols))); + } + return result; + } + +} diff --git a/src/main/java/jp/co/training/command/UpdateCommand.java b/src/main/java/jp/co/training/command/UpdateCommand.java new file mode 100644 index 0000000..d131a34 --- /dev/null +++ b/src/main/java/jp/co/training/command/UpdateCommand.java @@ -0,0 +1,49 @@ +package jp.co.training.command; + +import static jp.co.training.Const.*; + +import java.util.HashMap; +import java.util.Map; + +import jp.co.training.Const; + +import jp.co.training.common.Status; +import jp.co.training.dao.BookDao; +import jp.co.training.dao.DaoResult; + +public class UpdateCommand extends Command { + + public UpdateCommand(String name) { + super(name); + } + + @Override + public CommandResult executeCommand(String argments) { + CommandResult commandResult = new CommandResult(UPDATE); + + //argmentsを解析 + String[] params = argments.split(" " + Const.SET + " "); + if (params.length == 1) { + //TODO 構文エラーを返却 + } + + String targetId = params[0]; + String[] keyValues = params[1].split(","); + + //更新項目mapを生成 + Map updateParams = new HashMap<>(); + for (String keyValue : keyValues) { + //TODO正規表現でkey=valueの形式チェック + updateParams.put(keyValue.substring(0, keyValue.indexOf("=")), + keyValue.substring(keyValue.indexOf("=") + 1)); + } + + //更新実行 + DaoResult daoResult = new BookDao().update(targetId, updateParams); + commandResult.addCodes(daoResult.getCodes()); + commandResult.setStatus(Status.OK); + return commandResult; + + } + +} diff --git a/src/main/java/jp/co/training/common/Code.java b/src/main/java/jp/co/training/common/Code.java new file mode 100644 index 0000000..e1b6727 --- /dev/null +++ b/src/main/java/jp/co/training/common/Code.java @@ -0,0 +1,6 @@ +package jp.co.training.common; + +public interface Code { + + String getCode(); +} diff --git a/src/main/java/jp/co/training/common/Entity.java b/src/main/java/jp/co/training/common/Entity.java new file mode 100644 index 0000000..9958eee --- /dev/null +++ b/src/main/java/jp/co/training/common/Entity.java @@ -0,0 +1,28 @@ +package jp.co.training.common; + +public abstract class Entity { + + protected String createUser; + protected String createdDate; + protected String updateUser; + protected String updatedDate; + + public String getCreateUser() { + return createUser; + } + + public String getCreatedDate() { + return createdDate; + } + + public String getUpdateUser() { + return updateUser; + } + + public String getUpdatedDate() { + return updatedDate; + } + + public abstract String encode(); + +} diff --git a/src/main/java/jp/co/training/common/Result.java b/src/main/java/jp/co/training/common/Result.java new file mode 100644 index 0000000..a6944d5 --- /dev/null +++ b/src/main/java/jp/co/training/common/Result.java @@ -0,0 +1,15 @@ +package jp.co.training.common; + +public abstract class Result { + + private Status status = Status.NG; + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + +} diff --git a/src/main/java/jp/co/training/common/Status.java b/src/main/java/jp/co/training/common/Status.java new file mode 100644 index 0000000..1b084ac --- /dev/null +++ b/src/main/java/jp/co/training/common/Status.java @@ -0,0 +1,7 @@ +package jp.co.training.common; + +public enum Status { + + OK, NG + +} diff --git a/src/main/java/jp/co/training/dao/BookDao.java b/src/main/java/jp/co/training/dao/BookDao.java new file mode 100644 index 0000000..fb781ca --- /dev/null +++ b/src/main/java/jp/co/training/dao/BookDao.java @@ -0,0 +1,100 @@ +package jp.co.training.dao; + +import static jp.co.training.Main.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; + +import jp.co.training.book.Book; +import jp.co.training.common.Status; + +public class BookDao { + + public DaoResult insert(Book book) { + + DaoResult result = new DaoResult(); + + try (PrintWriter writer = new PrintWriter( + Files.newBufferedWriter(Paths.get(config.saveFile), StandardCharsets.UTF_8))) { + writer.println(book.toString()); + } catch (IOException ex) { + result.addCode(DaoCode.IO_ERROR); + return result; + } + + result.setStatus(Status.OK); + return result; + } + + public DaoResult update(String id, Map updateParams) { + //TODO 1行ずつ読取、1行ずつ書込 + + // String suffix = ".tmp" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddhhmmss")); + + // Book updateBook = null; + // try { + // updateBook = Book.createBook(argments); + // } catch (BookException e1) { + // result.addMessage(e1.getMessage()); + // return result; + // } + // + // Path saveFilePath = Paths.get(config.saveFile); + // Path tmpFilePath = Paths.get(config.saveFile + suffix); + // try (BufferedReader reader = Files.newBufferedReader(saveFilePath); + // PrintWriter writer = new PrintWriter(Files.newBufferedWriter(tmpFilePath, StandardCharsets.UTF_8))) { + // + // String recordLine; + // while ((recordLine = reader.readLine()) != null) { + // BookRecord record = BookRecord.decode(recordLine.split(config.delimiter)); + // //TODO 判定や変換処理 + // if (updateBook.getIsbn().equals(record.getBook().getIsbn())) { + // String today = LocalDateTime.now().format(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)); + // recordLine = new BookRecord.Builder() + // .id(record.getId()) + // .book(updateBook) + // .createUser(record.getCreateUser()) + // .createdDate(record.getCreatedDate()) + // .updateUser(config.userName) + // .updatedDate(today) + // .build().toString(); + // } + // writer.println(recordLine); + // } + // Files.delete(saveFilePath); + // Files.move(tmpFilePath, saveFilePath); + // } catch (IOException e) { + // e.printStackTrace(); + // } catch (BookException e) { + // result.addMessage("File format is invalid."); + // result.addMessage(e.getMessage()); + // return result; + // } + // return result; + + return null; + } + + public SelectDaoResult select() { + SelectDaoResult result = new SelectDaoResult<>(); + Path saveFilePath = Paths.get(config.saveFile); + try (BufferedReader reader = Files.newBufferedReader(saveFilePath)) { + + String recordLine; + while ((recordLine = reader.readLine()) != null) { + result.add(Book.decode(recordLine)); + } + } catch (IOException e) { + result.addCode(DaoCode.IO_ERROR); + return result; + } + return result; + } + +} diff --git a/src/main/java/jp/co/training/dao/DaoCode.java b/src/main/java/jp/co/training/dao/DaoCode.java new file mode 100644 index 0000000..2301134 --- /dev/null +++ b/src/main/java/jp/co/training/dao/DaoCode.java @@ -0,0 +1,20 @@ +package jp.co.training.dao; + +import jp.co.training.common.Code; + +public enum DaoCode implements Code { + + IO_ERROR("E003"); + + private final String code; + + DaoCode(String code) { + this.code = code; + } + + @Override + public String getCode() { + return this.code; + } + +} diff --git a/src/main/java/jp/co/training/dao/DaoResult.java b/src/main/java/jp/co/training/dao/DaoResult.java new file mode 100644 index 0000000..bbb608e --- /dev/null +++ b/src/main/java/jp/co/training/dao/DaoResult.java @@ -0,0 +1,25 @@ +package jp.co.training.dao; + +import java.util.HashSet; +import java.util.Set; + +import jp.co.training.common.Code; +import jp.co.training.common.Result; + +public class DaoResult extends Result { + + private Set codes = new HashSet<>(); + + public Set getCodes() { + return codes; + } + + public void addCode(Code code) { + codes.add(code); + } + + public void addCodes(Set codes) { + this.codes.addAll(codes); + } + +} diff --git a/src/main/java/jp/co/training/dao/SelectDaoResult.java b/src/main/java/jp/co/training/dao/SelectDaoResult.java new file mode 100644 index 0000000..656dae7 --- /dev/null +++ b/src/main/java/jp/co/training/dao/SelectDaoResult.java @@ -0,0 +1,18 @@ +package jp.co.training.dao; + +import java.util.ArrayList; +import java.util.List; + +public class SelectDaoResult extends DaoResult { + + private List records = new ArrayList<>(); + + public List getRecords() { + return records; + } + + public void add(T t) { + records.add(t); + } + +} diff --git a/src/main/java/jp/co/training/util/BookUtil.java b/src/main/java/jp/co/training/util/BookUtil.java new file mode 100644 index 0000000..d6e64fb --- /dev/null +++ b/src/main/java/jp/co/training/util/BookUtil.java @@ -0,0 +1,21 @@ +package jp.co.training.util; + +import java.security.SecureRandom; + +public class BookUtil { + + private BookUtil() { + } + + public static String generateID(int digit) { + SecureRandom random = new SecureRandom(); + byte bytes[] = new byte[digit / 2]; + random.nextBytes(bytes); + StringBuilder sb = new StringBuilder(); + for (byte d : bytes) { + sb.append(String.format("%02X", d)); + } + return sb.toString(); + } + +} diff --git a/src/main/java/jp/co/training/validate/ItemValidater.java b/src/main/java/jp/co/training/validate/ItemValidater.java new file mode 100644 index 0000000..a9adbfa --- /dev/null +++ b/src/main/java/jp/co/training/validate/ItemValidater.java @@ -0,0 +1,64 @@ +package jp.co.training.validate; + +import static jp.co.training.book.ItemCode.*; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Optional; +import java.util.regex.Pattern; + +import jp.co.training.book.ItemCode; + +public class ItemValidater { + + private ItemValidater() { + } + + /** + * 対象文字列が指定された最小文字数と最大文字数の範囲内であるかをチェックします
+ * 範囲内である場合はOptionalの中身はnullとなる
+ * 範囲外である場合はOptionalの中身はエラーメッセージとなる
+ * + * @param target + * @param min + * @param max + * @return + */ + public static Optional checkLength(String target, int min, int max) { + if (target == null || target.length() < min) { + return Optional.of(MIN_LENGTH_UNDER); + } else if (target.length() > max) { + return Optional.of(MAX_LENGTH_OVER); + } + return Optional.empty(); + } + + public static Optional checkDatePattern(String target, String pattern) { + DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern); + try { + LocalDate.parse(target, df); + } catch (DateTimeParseException e) { + return Optional.of(INVALID_DATE_PATTERN); + } + return Optional.empty(); + } + + public static Optional checkNumber(String target) { + if (!isNumber(target)) { + return Optional.of(NOT_NUMERICAL); + } + return Optional.empty(); + } + + /** + * valueが数値文字列の場合、または空文字の場合はtrueを返す + * + * @param value + * @return + */ + private static boolean isNumber(String value) { + return Pattern.compile("^[0-9]*$").matcher(value).matches(); + } + +} diff --git a/src/main/resources/messages b/src/main/resources/messages new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/resource.properties b/src/main/resources/resource.properties index e8bfc1f..593f94c 100644 --- a/src/main/resources/resource.properties +++ b/src/main/resources/resource.properties @@ -1 +1 @@ -aa=aaa \ No newline at end of file +user.name=yamada taro \ No newline at end of file diff --git a/src/test/java/jp/co/training/BookTest.java b/src/test/java/jp/co/training/BookTest.java new file mode 100644 index 0000000..3cfe942 --- /dev/null +++ b/src/test/java/jp/co/training/BookTest.java @@ -0,0 +1,55 @@ +package jp.co.training; + +import static jp.co.training.Const.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import jp.co.training.book.Book; +import jp.co.training.command.CommandResult; + +class BookTest { + + @Nested + @DisplayName("validateメソッドのテスト") + class validateTest { + + @Test + @DisplayName("ISBNの文字数チェックエラー") + void validateIsbnNG() { + Book book = new Book.Builder().isbn("12345678901234") + .bookName("社長も投票で決める会社をやってみた") + .author("武井 浩三") + .publisher("WAVE出版") + .publicationDate("20180423") + .price("1620").build(); + CommandResult result = book.validate(); + assertFalse(result.isExit()); + assertEquals(1, result.getMesages().size()); + assertEquals("ISBM:the length must be " + MAX_ISBN + " or less. but actual is 14.", + result.getMesages().get(0)); + } + + } + + @Nested + @DisplayName("toStringメソッドのテスト") + class toStringTest { + + } + + @Nested + @DisplayName("Builderクラスのテスト") + class BuilderTest { + + @Nested + @DisplayName("Buildメソッドのテスト") + class buildTest { + + } + + } + +} diff --git a/src/test/java/jp/co/training/BookUtilTest.java b/src/test/java/jp/co/training/BookUtilTest.java new file mode 100644 index 0000000..1230c59 --- /dev/null +++ b/src/test/java/jp/co/training/BookUtilTest.java @@ -0,0 +1,70 @@ +package jp.co.training; + +import static jp.co.training.Const.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import jp.co.training.validate.ItemValidater; + +class BookUtilTest { + + @Nested + class CheckLengthTest { + @Test + @DisplayName("最大文字超過チェックOK") + void maxOverOK() { + Optional result = ItemValidater.checkLength("あいう", 1, 3); + assertFalse(result.isPresent()); + } + + @Test + @DisplayName("最小文字超過チェックOK") + void minUnderOK() { + Optional result = ItemValidater.checkLength("あ", 1, 3); + assertFalse(result.isPresent()); + } + + @Test + @DisplayName("最大文字超過チェックNG") + void maxOverNG() { + Optional result = ItemValidater.checkLength("あいうえ", 1, 3); + assertEquals("the length must be " + 3 + " or less. but actual is " + 4 + ".", result.get()); + } + + @Test + @DisplayName("最小文字超過チェックNG") + void minUnderNG() { + Optional result = ItemValidater.checkLength("", 1, 3); + assertEquals("the length must be " + 1 + " or more. but actual is " + 0 + ".", result.get()); + } + + @Test + @DisplayName("nullチェックNG") + void nullCheckNG() { + Optional result = ItemValidater.checkLength(null, 1, 3); + assertEquals("the length must be " + 1 + " or more. but actual is null.", result.get()); + } + } + + @Nested + class checkDatePatternTest { + @Test + @DisplayName("有効な日付形式OK") + void datePatternOK() { + Optional result = ItemValidater.checkDatePattern("20180815", DATE_PATTERN); + assertFalse(result.isPresent()); + } + + @Test + @DisplayName("無効な日付形式NG") + void datePatternNG() { + Optional result = ItemValidater.checkDatePattern("20181815", DATE_PATTERN); + assertEquals("Invalid Pattern. valid pattern is " + DATE_PATTERN + ".", result.get()); + } + } +}