001package io.ebean.migration;
002
003import org.slf4j.Logger;
004import org.slf4j.LoggerFactory;
005
006import java.util.Arrays;
007
008/**
009 * The version of a migration used so that migrations are processed in order.
010 */
011public class MigrationVersion implements Comparable<MigrationVersion> {
012
013  private static final Logger logger = LoggerFactory.getLogger(MigrationVersion.class);
014
015  public static final String BOOTINIT_TYPE = "B";
016
017  private static final String INIT_TYPE = "I";
018
019  private static final String REPEAT_TYPE = "R";
020
021  private static final String VERSION_TYPE = "V";
022
023  private static final int[] REPEAT_ORDERING_MIN = {Integer.MIN_VALUE};
024
025  private static final int[] REPEAT_ORDERING_MAX = {Integer.MAX_VALUE};
026
027  private static final boolean[] REPEAT_UNDERSCORES = {false};
028
029  /**
030   * The raw version text.
031   */
032  private final String raw;
033
034  /**
035   * The ordering parts.
036   */
037  private final int[] ordering;
038
039  private final boolean[] underscores;
040
041  private final String comment;
042
043  /**
044   * Construct for "repeatable" version.
045   */
046  private MigrationVersion(String raw, String comment, boolean init) {
047    this.raw = raw;
048    this.comment = comment;
049    this.ordering = init ? REPEAT_ORDERING_MIN : REPEAT_ORDERING_MAX;
050    this.underscores = REPEAT_UNDERSCORES;
051  }
052
053  /**
054   * Construct for "normal" version.
055   */
056  private MigrationVersion(String raw, int[] ordering, boolean[] underscores, String comment) {
057    this.raw = raw;
058    this.ordering = ordering;
059    this.underscores = underscores;
060    this.comment = comment;
061  }
062
063  /**
064   * Return true if this is a "repeatable" version.
065   */
066  public boolean isRepeatable() {
067    return ordering == REPEAT_ORDERING_MIN || ordering == REPEAT_ORDERING_MAX;
068  }
069
070  /**
071   * Return true if this is a "repeatable init" verision.
072   */
073  public boolean isRepeatableInit() {
074    return ordering == REPEAT_ORDERING_MIN;
075  }
076
077  /**
078   * Return the full version.
079   */
080  public String getFull() {
081    return raw;
082  }
083
084  public String toString() {
085    return raw;
086  }
087
088  /**
089   * Return the version comment.
090   */
091  public String getComment() {
092    return comment;
093  }
094
095  /**
096   * Return the version in raw form.
097   */
098  public String getRaw() {
099    return raw;
100  }
101
102  /**
103   * Return the trimmed version excluding version comment and un-parsable string.
104   */
105  public String asString() {
106    return formattedVersion(false, false);
107  }
108
109  /**
110   * Return the trimmed version with any underscores replaced with '.'
111   */
112  public String normalised() {
113    return formattedVersion(true, false);
114  }
115
116  /**
117   * Return the next version based on this version.
118   */
119  public String nextVersion() {
120    return formattedVersion(false, true);
121  }
122
123  /**
124   * Returns the version part of the string.
125   * <p>
126   * Normalised means always use '.' delimiters (no underscores).
127   * NextVersion means bump/increase the last version number by 1.
128   */
129  private String formattedVersion(boolean normalised, boolean nextVersion) {
130
131    if (isRepeatable()) {
132      return getType();
133    }
134    StringBuilder sb = new StringBuilder();
135    for (int i = 0; i < ordering.length; i++) {
136      if (i < ordering.length - 1) {
137        sb.append(ordering[i]);
138        if (normalised) {
139          sb.append('.');
140        } else {
141          sb.append(underscores[i] ? '_' : '.');
142        }
143      } else {
144        sb.append((nextVersion) ? ordering[i] + 1 : ordering[i]);
145      }
146    }
147    return sb.toString();
148  }
149
150  @Override
151  public int compareTo(MigrationVersion other) {
152
153    int otherLength = other.ordering.length;
154    for (int i = 0; i < ordering.length; i++) {
155      if (i >= otherLength) {
156        // considered greater
157        return 1;
158      }
159      if (ordering[i] != other.ordering[i]) {
160        return (ordering[i] > other.ordering[i]) ? 1 : -1;
161      }
162    }
163    if (ordering.length < otherLength) {
164      return -1;
165    }
166    return isRepeatable() ? comment.compareTo(other.comment) : 0;
167  }
168
169  /**
170   * Parse the raw version string and just return the leading version number;
171   */
172  public static String trim(String raw) {
173    return parse(raw).asString();
174  }
175
176  /**
177   * Parse the raw version string into a MigrationVersion.
178   */
179  public static MigrationVersion parse(String raw) {
180
181    if (raw.startsWith("V") || raw.startsWith("v")) {
182      raw = raw.substring(1);
183    }
184
185    String comment = "";
186    String value = raw;
187    int commentStart = raw.indexOf("__");
188    if (commentStart > -1) {
189      // trim off the trailing comment
190      comment = raw.substring(commentStart + 2);
191      value = value.substring(0, commentStart);
192    }
193
194    value = value.replace('_', '.');
195
196    String[] sections = value.split("[\\.-]");
197
198    if (sections[0].startsWith("R") || sections[0].startsWith("r")) {
199      // a "repeatable" version (does not have a version number)
200      return new MigrationVersion(raw, comment, false);
201    }
202
203    if (sections[0].startsWith("I") || sections[0].startsWith("i")) {
204      // this script will be executed before all other scripts
205      return new MigrationVersion(raw, comment, true);
206    }
207
208    boolean[] underscores = new boolean[sections.length];
209    int[] ordering = new int[sections.length];
210
211    int delimiterPos = 0;
212    int stopIndex = 0;
213    for (int i = 0; i < sections.length; i++) {
214      try {
215        ordering[i] = Integer.parseInt(sections[i]);
216        stopIndex++;
217
218        delimiterPos += sections[i].length();
219        underscores[i] = (delimiterPos < raw.length() - 1 && raw.charAt(delimiterPos) == '_');
220        delimiterPos++;
221      } catch (NumberFormatException e) {
222        // stop parsing
223        logger.warn("The migrationscript '{}' contains non numeric version part. "
224          + "This may lead to misordered version scripts. NumberFormatException {}", raw, e.getMessage());
225        break;
226      }
227    }
228
229    int[] actualOrder = Arrays.copyOf(ordering, stopIndex);
230    boolean[] actualUnderscores = Arrays.copyOf(underscores, stopIndex);
231
232    return new MigrationVersion(raw, actualOrder, actualUnderscores, comment);
233  }
234
235  /**
236   * Return the version type (I, R or V).
237   */
238  public String getType() {
239    if (ordering == REPEAT_ORDERING_MIN) {
240      return INIT_TYPE;
241
242    } else if (ordering == REPEAT_ORDERING_MAX) {
243      return REPEAT_TYPE;
244
245    } else {
246      return VERSION_TYPE;
247    }
248  }
249}