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