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}