MigrationReader.java

  1. /*
  2.  *    Copyright 2010-2023 the original author or authors.
  3.  *
  4.  *    Licensed under the Apache License, Version 2.0 (the "License");
  5.  *    you may not use this file except in compliance with the License.
  6.  *    You may obtain a copy of the License at
  7.  *
  8.  *       https://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  *    Unless required by applicable law or agreed to in writing, software
  11.  *    distributed under the License is distributed on an "AS IS" BASIS,
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  *    See the License for the specific language governing permissions and
  14.  *    limitations under the License.
  15.  */
  16. package org.apache.ibatis.migration;

  17. import java.io.File;
  18. import java.io.FileInputStream;
  19. import java.io.FilterReader;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.InputStreamReader;
  23. import java.io.Reader;
  24. import java.nio.charset.Charset;
  25. import java.util.Properties;

  26. public class MigrationReader extends FilterReader {

  27.   // Note: Cannot use lineSeparator directly at this time due to tests manipulating it
  28.   private final String lineSeparator = System.getProperty("line.separator", "\n");

  29.   private static final String UNDO_TAG = "@UNDO";

  30.   private boolean undo;

  31.   private Properties variables;

  32.   private Part part = Part.NEW_LINE;

  33.   private VariableStatus variableStatus = VariableStatus.NOTHING;

  34.   private char previousChar;

  35.   private int undoIndex;

  36.   private int afterCommentPrefixIndex;

  37.   private int afterDoubleSlashIndex;

  38.   private boolean inUndo;

  39.   private final StringBuilder buffer = new StringBuilder();

  40.   private final StringBuilder lineBuffer = new StringBuilder();

  41.   private final VariableReplacer replacer;

  42.   private enum Part {
  43.     NOT_UNDO_LINE,

  44.     NEW_LINE,

  45.     COMMENT_PREFIX,

  46.     AFTER_COMMENT_PREFIX,

  47.     DOUBLE_SLASH,

  48.     AFTER_DOUBLE_SLASH,

  49.     UNDO_TAG,

  50.     AFTER_UNDO_TAG
  51.   }

  52.   private enum VariableStatus {
  53.     NOTHING,

  54.     FOUND_DOLLAR,

  55.     FOUND_OPEN_BRACE,

  56.     FOUND_POSSIBLE_VARIABLE
  57.   }

  58.   public MigrationReader(File file, String charset, boolean undo, Properties variables) throws IOException {
  59.     this(new FileInputStream(file), charset, undo, variables);
  60.   }

  61.   public MigrationReader(InputStream inputStream, String charset, boolean undo, Properties variables) {
  62.     super(scriptFileReader(inputStream, charset));
  63.     this.undo = undo;
  64.     this.variables = variables;
  65.     replacer = new VariableReplacer(this.variables);
  66.   }

  67.   @Override
  68.   public int read(char[] cbuf, int off, int len) throws IOException {
  69.     if (!undo && inUndo) {
  70.       if (buffer.length() > 0) {
  71.         return readFromBuffer(cbuf, off, len);
  72.       }
  73.       return -1;
  74.     }
  75.     while (buffer.length() == 0) {
  76.       int result = in.read(cbuf, off, len);
  77.       if (result == -1) {
  78.         if (lineBuffer.length() > 0 && (!undo || inUndo)) {
  79.           addToBuffer(lineBuffer);
  80.         }
  81.         if (buffer.length() > 0) {
  82.           break;
  83.         }
  84.         return -1;
  85.       }

  86.       for (int i = off; i < off + result; i++) {
  87.         char c = cbuf[i];

  88.         determinePart(c);
  89.         searchVariable(c);

  90.         if (c == '\r' || c == '\n' && previousChar != '\r') {
  91.           switch (part) {
  92.             case AFTER_UNDO_TAG:
  93.               if (!undo) {
  94.                 // Won't read from the file anymore.
  95.                 lineBuffer.setLength(0);
  96.                 int bufferLen = buffer.length();
  97.                 if (bufferLen == 0) {
  98.                   return -1;
  99.                 }
  100.                 return readFromBuffer(cbuf, off, len);
  101.               }
  102.               addToBuffer(lineBuffer.delete(afterCommentPrefixIndex, afterDoubleSlashIndex)
  103.                   .insert(afterCommentPrefixIndex, ' '));
  104.               inUndo = true;
  105.               break;
  106.             case NOT_UNDO_LINE:
  107.               if (!undo || inUndo) {
  108.                 addToBuffer(lineBuffer);
  109.               } else {
  110.                 lineBuffer.setLength(0);
  111.               }
  112.               break;
  113.             default:
  114.               break;
  115.           }
  116.           part = Part.NEW_LINE;
  117.         } else if (c == '\n') {
  118.           // LF after CR
  119.           part = Part.NEW_LINE;
  120.         } else {
  121.           lineBuffer.append(c);
  122.         }
  123.         previousChar = c;
  124.       }
  125.     }
  126.     return readFromBuffer(cbuf, off, len);
  127.   }

  128.   private void addToBuffer(StringBuilder line) {
  129.     replaceVariables(line);
  130.     buffer.append(line).append(lineSeparator);
  131.     lineBuffer.setLength(0);
  132.   }

  133.   private void replaceVariables(StringBuilder line) {
  134.     if (variableStatus == VariableStatus.FOUND_POSSIBLE_VARIABLE) {
  135.       String lineBufferStr = line.toString();
  136.       String processed = replacer.replace(lineBufferStr);
  137.       if (!lineBufferStr.equals(processed)) {
  138.         line.setLength(0);
  139.         line.append(processed);
  140.       }
  141.     }
  142.     variableStatus = VariableStatus.NOTHING;
  143.   }

  144.   private int readFromBuffer(char[] cbuf, int off, int len) {
  145.     int bufferLen = buffer.length();
  146.     int read = bufferLen > len ? len : bufferLen;
  147.     buffer.getChars(0, read, cbuf, off);
  148.     buffer.delete(0, read);
  149.     return read;
  150.   }

  151.   private void determinePart(char c) {
  152.     switch (part) {
  153.       case NEW_LINE:
  154.         if (inUndo) {
  155.           part = Part.NOT_UNDO_LINE;
  156.         } else if (c == 0x09 || c == 0x20) {
  157.           // ignore whitespace
  158.         } else if (c == '/' || c == '-') {
  159.           part = Part.COMMENT_PREFIX;
  160.         } else {
  161.           part = Part.NOT_UNDO_LINE;
  162.         }
  163.         break;
  164.       case COMMENT_PREFIX:
  165.         if ((c == '/' || c == '-') && c == previousChar) {
  166.           part = Part.AFTER_COMMENT_PREFIX;
  167.           afterCommentPrefixIndex = lineBuffer.length() + 1;
  168.         } else {
  169.           part = Part.NOT_UNDO_LINE;
  170.         }
  171.         break;
  172.       case AFTER_COMMENT_PREFIX:
  173.         if (c == 0x09 || c == 0x20) {
  174.           // ignore whitespace
  175.         } else if (c == '/') {
  176.           part = Part.DOUBLE_SLASH;
  177.         } else {
  178.           part = Part.NOT_UNDO_LINE;
  179.         }
  180.         break;
  181.       case DOUBLE_SLASH:
  182.         if (c == '/' && c == previousChar) {
  183.           part = Part.AFTER_DOUBLE_SLASH;
  184.           afterDoubleSlashIndex = lineBuffer.length() + 1;
  185.           undoIndex = 0;
  186.         } else {
  187.           part = Part.NOT_UNDO_LINE;
  188.         }
  189.         break;
  190.       case AFTER_DOUBLE_SLASH:
  191.         if (c == 0x09 || c == 0x20) {
  192.           // ignore whitespace
  193.         } else if (c == UNDO_TAG.charAt(undoIndex)) {
  194.           part = Part.UNDO_TAG;
  195.           undoIndex = 1;
  196.         } else {
  197.           part = Part.NOT_UNDO_LINE;
  198.         }
  199.         break;
  200.       case UNDO_TAG:
  201.         if (c != UNDO_TAG.charAt(undoIndex)) {
  202.           part = Part.NOT_UNDO_LINE;
  203.         } else if (++undoIndex >= UNDO_TAG.length()) {
  204.           part = Part.AFTER_UNDO_TAG;
  205.         }
  206.         break;
  207.       default:
  208.         break;
  209.     }
  210.   }

  211.   private void searchVariable(char c) {
  212.     // This is just a quick check.
  213.     switch (variableStatus) {
  214.       case NOTHING:
  215.         if ((part == Part.NOT_UNDO_LINE || part == Part.AFTER_UNDO_TAG) && c == '$') {
  216.           variableStatus = VariableStatus.FOUND_DOLLAR;
  217.         }
  218.         break;
  219.       case FOUND_DOLLAR:
  220.         variableStatus = c == '{' ? VariableStatus.FOUND_OPEN_BRACE : VariableStatus.NOTHING;
  221.         break;
  222.       case FOUND_OPEN_BRACE:
  223.         if (c == '}') {
  224.           variableStatus = VariableStatus.FOUND_POSSIBLE_VARIABLE;
  225.         }
  226.         break;
  227.       default:
  228.         break;
  229.     }
  230.   }

  231.   @Override
  232.   public int read() throws IOException {
  233.     char[] buf = new char[1];
  234.     int result = read(buf, 0, 1);
  235.     return result == -1 ? -1 : (int) buf[0];
  236.   }

  237.   protected static Reader scriptFileReader(InputStream inputStream, String charset) {
  238.     if (charset == null || charset.length() == 0) {
  239.       return new InputStreamReader(inputStream);
  240.     }
  241.     return new InputStreamReader(inputStream, Charset.forName(charset));
  242.   }
  243. }