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 public 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 true if this is a "repeatable last" verision. 079 */ 080 public boolean isRepeatableLast() { 081 return ordering == REPEAT_ORDERING_MAX; 082 } 083 084 /** 085 * Return the full version. 086 */ 087 public String getFull() { 088 return raw; 089 } 090 091 public String toString() { 092 return raw; 093 } 094 095 /** 096 * Return the version comment. 097 */ 098 public String getComment() { 099 return comment; 100 } 101 102 /** 103 * Return the version in raw form. 104 */ 105 public String getRaw() { 106 return raw; 107 } 108 109 /** 110 * Return the trimmed version excluding version comment and un-parsable string. 111 */ 112 public String asString() { 113 return formattedVersion(false, false); 114 } 115 116 /** 117 * Return the trimmed version with any underscores replaced with '.' 118 */ 119 public String normalised() { 120 return formattedVersion(true, false); 121 } 122 123 /** 124 * Return the next version based on this version. 125 */ 126 public String nextVersion() { 127 return formattedVersion(false, true); 128 } 129 130 /** 131 * Returns the version part of the string. 132 * <p> 133 * Normalised means always use '.' delimiters (no underscores). 134 * NextVersion means bump/increase the last version number by 1. 135 */ 136 private String formattedVersion(boolean normalised, boolean nextVersion) { 137 138 if (isRepeatable()) { 139 return getType(); 140 } 141 StringBuilder sb = new StringBuilder(); 142 for (int i = 0; i < ordering.length; i++) { 143 if (i < ordering.length - 1) { 144 sb.append(ordering[i]); 145 if (normalised) { 146 sb.append('.'); 147 } else { 148 sb.append(underscores[i] ? '_' : '.'); 149 } 150 } else { 151 sb.append((nextVersion) ? ordering[i] + 1 : ordering[i]); 152 } 153 } 154 return sb.toString(); 155 } 156 157 @Override 158 public int compareTo(MigrationVersion other) { 159 160 int otherLength = other.ordering.length; 161 for (int i = 0; i < ordering.length; i++) { 162 if (i >= otherLength) { 163 // considered greater 164 return 1; 165 } 166 if (ordering[i] != other.ordering[i]) { 167 return (ordering[i] > other.ordering[i]) ? 1 : -1; 168 } 169 } 170 if (ordering.length < otherLength) { 171 return -1; 172 } 173 return isRepeatable() ? comment.compareTo(other.comment) : 0; 174 } 175 176 /** 177 * Parse the raw version string and just return the leading version number; 178 */ 179 public static String trim(String raw) { 180 return parse(raw).asString(); 181 } 182 183 /** 184 * Parse the raw version string into a MigrationVersion. 185 */ 186 public static MigrationVersion parse(String raw) { 187 188 if (raw.startsWith("V") || raw.startsWith("v")) { 189 raw = raw.substring(1); 190 } 191 192 String comment = ""; 193 String value = raw; 194 int commentStart = raw.indexOf("__"); 195 if (commentStart > -1) { 196 // trim off the trailing comment 197 comment = raw.substring(commentStart + 2); 198 value = value.substring(0, commentStart); 199 } 200 201 value = value.replace('_', '.'); 202 203 String[] sections = value.split("[\\.-]"); 204 205 if (sections[0].startsWith("R") || sections[0].startsWith("r")) { 206 // a "repeatable" version (does not have a version number) 207 return new MigrationVersion(raw, comment, false); 208 } 209 210 if (sections[0].startsWith("I") || sections[0].startsWith("i")) { 211 // this script will be executed before all other scripts 212 return new MigrationVersion(raw, comment, true); 213 } 214 215 boolean[] underscores = new boolean[sections.length]; 216 int[] ordering = new int[sections.length]; 217 218 int delimiterPos = 0; 219 int stopIndex = 0; 220 for (int i = 0; i < sections.length; i++) { 221 try { 222 ordering[i] = Integer.parseInt(sections[i]); 223 stopIndex++; 224 225 delimiterPos += sections[i].length(); 226 underscores[i] = (delimiterPos < raw.length() - 1 && raw.charAt(delimiterPos) == '_'); 227 delimiterPos++; 228 } catch (NumberFormatException e) { 229 // stop parsing 230 logger.warn("The migrationscript '{}' contains non numeric version part. " 231 + "This may lead to misordered version scripts. NumberFormatException {}", raw, e.getMessage()); 232 break; 233 } 234 } 235 236 int[] actualOrder = Arrays.copyOf(ordering, stopIndex); 237 boolean[] actualUnderscores = Arrays.copyOf(underscores, stopIndex); 238 239 return new MigrationVersion(raw, actualOrder, actualUnderscores, comment); 240 } 241 242 /** 243 * Return the version type (I, R or V). 244 */ 245 public String getType() { 246 if (ordering == REPEAT_ORDERING_MIN) { 247 return INIT_TYPE; 248 249 } else if (ordering == REPEAT_ORDERING_MAX) { 250 return REPEAT_TYPE; 251 252 } else { 253 return VERSION_TYPE; 254 } 255 } 256}