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