package com.github.maven_nar;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.tools.ant.types.Environment.Variable;
import org.codehaus.plexus.util.StringUtils;
import com.github.maven_nar.cpptasks.CCTask;
import com.github.maven_nar.cpptasks.CompilerDef;
import com.github.maven_nar.cpptasks.LinkerDef;
import com.github.maven_nar.cpptasks.types.SystemIncludePath;
import com.google.common.collect.Sets;
public class Msvc {
@Parameter
private File home;
private AbstractNarMojo mojo;
private final Set<String> paths = new LinkedHashSet<>();
/**
* VisualStudio Linker version. Required. The values should be:
* <ul>
* <li>7.1 for VS 2003</li>
* <li>8.0 for VS 2005</li>
* <li>9.0 for VS 2008</li>
* <li>10.0 for VS 2010</li>
* <li>11.0 for VS 2012</li>
* <li>12.00 for VS 2013</li>
* <li>14.0 for VS 2015</li>
* <li>15.0 for VS 2017</li>
* </ul>
*/
@Parameter(defaultValue = "")
private String version;
@Parameter
private File windowsSdkHome;
@Parameter
private String windowsSdkVersion;
@Parameter
private String tempPath;
private File windowsHome;
private String toolPathWindowsSDK;
private String toolPathLinker;
private List<File> sdkIncludes = new ArrayList<>();
private List<File> sdkLibs = new ArrayList<>();
private Set<String> libsRequired = Sets.newHashSet("ucrt", "um", "shared", "winrt");
@Parameter(defaultValue = "false")
private boolean force_requested_arch;
private boolean addIncludePath(final CCTask task, final File home, final String subDirectory)
throws MojoExecutionException {
if (home == null) {
return false;
}
final File file = new File(home, subDirectory);
if (file.exists())
return addIncludePathToTask(task, file);
return false;
}
private boolean addIncludePathToTask(final CCTask task, final File file)
throws MojoExecutionException {
try {
final SystemIncludePath includePath = task.createSysIncludePath();
final String fullPath = file.getCanonicalPath();
includePath.setPath(fullPath);
return true;
}
catch (final IOException e) {
throw new MojoExecutionException("Unable to add system include: " + file.getAbsolutePath(), e);
}
}
private boolean addPath(final File home, final String path) {
if (home != null) {
final File directory = new File(home, path);
if (directory.exists()) {
try {
final String fullPath = directory.getCanonicalPath();
paths.add(fullPath);
return true;
}
catch (final IOException e) {
throw new IllegalArgumentException("Unable to get path: " + directory, e);
}
}
}
return false;
}
static boolean isMSVC(final AbstractNarMojo mojo) {
return isMSVC(mojo.getLinker().getName());
}
static boolean isMSVC(final String name) {
return "msvc".equalsIgnoreCase(name);
}
public void configureCCTask(final CCTask task) throws MojoExecutionException {
if (OS.WINDOWS.equals(mojo.getOS()) && isMSVC(mojo)) {
addIncludePath(task, home, "VC/include");
addIncludePath(task, home, "VC/atlmfc/include");
if (compareVersion(windowsSdkVersion, "7.1A") <= 0) {
if (version.equals("8.0")) {
// For VS 2005 the version of SDK is 2.0, but it needs more paths
for (File sdkInclude : sdkIncludes) {
addIncludePathToTask(task, sdkInclude);
mojo.getLog().debug(" configureCCTask add to Path-- " + sdkInclude.getAbsolutePath());
}
}
else {
addIncludePath(task, windowsSdkHome, "include");
}
}
else {
for (File sdkInclude : sdkIncludes) {
addIncludePathToTask(task, sdkInclude);
}
}
task.addEnv(getPathVariable());
// TODO: supporting running with clean environment - addEnv sets
// newEnvironemnt by default
// task.setNewenvironment(false);
Variable envVariable = new Variable();
// cl needs SystemRoot env var set, otherwise D8037 is raised (bogus
// message)
// - https://msdn.microsoft.com/en-us/library/bb385201.aspx
// -
// http://stackoverflow.com/questions/10560779/cl-exe-when-launched-via-createprocess-does-not-seem-to-have-write-permissions
envVariable.setKey("SystemRoot");
envVariable.setValue(windowsHome.getAbsolutePath());
task.addEnv(envVariable);
// cl needs TMP otherwise D8050 is raised c1xx.dll
envVariable = new Variable();
envVariable.setKey("TMP");
envVariable.setValue(getTempPath());
task.addEnv(envVariable);
final String envInclude = System.getenv("INCLUDE");
if (envInclude != null) {
for (final String path : envInclude.split(";")) {
addIncludePathToTask(task, new File(path));
}
}
}
}
public void configureLinker(final LinkerDef linker) throws MojoExecutionException {
final String os = mojo.getOS();
if (os.equals(OS.WINDOWS) && isMSVC(mojo)) {
final String arch = mojo.getArchitecture();
// Visual Studio
if ("x86".equals(arch)) {
linker.addLibraryDirectory(home, "VC/lib");
linker.addLibraryDirectory(home, "VC/atlmfc/lib");
}
else {
linker.addLibraryDirectory(home, "VC/lib/" + arch);
linker.addLibraryDirectory(home, "VC/atlmfc/lib/" + arch);
}
// Windows SDK
String sdkArch = arch;
if ("amd64".equals(arch)) {
sdkArch = "x64";
}
// 6 lib ?+ lib/x86 or lib/x64
if (compareVersion(windowsSdkVersion, "8.0") < 0) {
if ("x86".equals(arch)) {
linker.addLibraryDirectory(windowsSdkHome, "lib");
}
else {
linker.addLibraryDirectory(windowsSdkHome, "lib/" + sdkArch);
}
}
else {
for (File sdkLib : sdkLibs) {
linker.addLibraryDirectory(sdkLib, sdkArch);
}
}
final String envLib = System.getenv("LIB");
if (envLib != null) {
for (final String path : envLib.split(";")) {
linker.addLibraryDirectory(new File(path));
}
}
}
}
private String getTempPath(){
if (null == tempPath) {
tempPath = System.getenv("TMP");
if (tempPath == null) tempPath = System.getenv("TEMP");
if (tempPath == null) tempPath = "C:\\Temp";
}
return tempPath;
}
public Variable getPathVariable() {
if (paths.isEmpty()) return null;
final Variable pathVariable = new Variable();
pathVariable.setKey("PATH");
pathVariable.setValue(StringUtils.join(paths.iterator(), File.pathSeparator));
return pathVariable;
}
public String getVersion() {
return version;
}
public String getWindowsSdkVersion() {
return windowsSdkVersion;
}
private void init() throws MojoFailureException, MojoExecutionException {
final String mojoOs = mojo.getOS();
if (NarUtil.isWindows() && OS.WINDOWS.equals(mojoOs) && isMSVC(mojo)) {
windowsHome = new File(System.getenv("SystemRoot"));
initVisualStudio();
if (version.equals("8.0")) {
// VS 2005 works with build in Windows SDK
initWindowsSdk8();
initPath8();
}
else {
initWindowsSdk();
initPath();
}
}
else {
version = "";
windowsSdkVersion = "";
windowsHome = null;
}
}
private void initPath() throws MojoExecutionException {
final String mojoArchitecture = mojo.getArchitecture();
final String osArchitecture = NarUtil.getArchitecture(null);
// 32 bit build on 64 bit OS can be built with 32 bit tool, or 64 bit tool
// in amd64_x86 - currently defaulting to prefer 64 bit tools - match os
final boolean matchMojo = false;
// TODO: toolset architecture
// match os - os x86 mojo(x86 / x86_amd64); os x64 mojo(amd64_x86 / amd64);
// 32bit - force 32 on 64bit mojo(x86 / x86_amd64)
// match mojo - os x86 is as above; os x64 mojo (x86 / amd64)
// Cross tools first if necessary, platform tools second, more generic tools
// later
if (force_requested_arch)
{
if ("amd64".equals(mojoArchitecture) && !matchMojo) {
addPath(home, "VC/bin/amd64");
toolPathLinker = new File(home, "VC/bin/amd64").getAbsolutePath();
}
else {
addPath(home, "VC/bin");
toolPathLinker = new File(home, "VC/bin").getAbsolutePath();
}
}
else if (!osArchitecture.equals(mojoArchitecture) && !matchMojo) {
if (!addPath(home, "VC/bin/" + osArchitecture + "_" + mojoArchitecture)) {
throw new MojoExecutionException("Unable to find compiler for architecture " + mojoArchitecture + ".\n"
+ new File(home, "VC/bin/" + osArchitecture + "_" + mojoArchitecture));
}
toolPathLinker = new File(home, "VC/bin/" + osArchitecture + "_" + mojoArchitecture).getAbsolutePath();
}
if (null == toolPathLinker) {
if ("amd64".equals(mojoArchitecture)) {
toolPathLinker = new File(home, "VC/bin/amd64").getAbsolutePath();
if (!new File(toolPathLinker).exists()) {
final String envVCToolsInstallDir = System.getenv("VCToolsInstallDir");
if (envVCToolsInstallDir != null) {
toolPathLinker = new File(envVCToolsInstallDir, "bin/HostX64/x64").getAbsolutePath();
}
}
}
else {
toolPathLinker = new File(home, "VC/bin").getAbsolutePath();
}
}
if ("amd64".equals(osArchitecture) && !matchMojo) {
addPath(home, "VC/bin/amd64");
}
else {
addPath(home, "VC/bin");
}
addPath(home, "VC/VCPackages");
addPath(home, "Common7/Tools");
addPath(home, "Common7/IDE");
// 64 bit tools if present are preferred
if (compareVersion(windowsSdkVersion, "7.1A") <= 0) {
if ("amd64".equals(osArchitecture) && !matchMojo) {
addPath(windowsSdkHome, "bin/x64");
}
addPath(windowsSdkHome, "bin");
}
else {
if ("amd64".equals(osArchitecture) && !matchMojo) {
addPath(windowsSdkHome, "bin/x64");
}
addPath(windowsSdkHome, "bin/x86");
}
if ("amd64".equals(mojoArchitecture)) {
toolPathWindowsSDK = new File(windowsSdkHome, "bin/x64").getAbsolutePath();
}
else if (compareVersion(windowsSdkVersion, "7.1A") <= 0) {
toolPathWindowsSDK = new File(windowsSdkHome, "bin").getAbsolutePath();
}
else {
toolPathWindowsSDK = new File(windowsSdkHome, "bin/x86").getAbsolutePath();
}
// clearing the path, add back the windows system folders
addPath(windowsHome, "System32");
addPath(windowsHome, "");
addPath(windowsHome, "System32/wbem");
}
private void initPath8() throws MojoExecutionException {
final String mojoArchitecture = mojo.getArchitecture();
final String osArchitecture = NarUtil.getArchitecture(null);
// 32 bit build on 64 bit OS can be built with 32 bit tool, or 64 bit tool
// in amd64_x86 - currently defaulting to prefer 64 bit tools - match os
final boolean matchMojo = false;
// TODO: toolset architecture
// match os - os x86 mojo(x86 / x86_amd64); os x64 mojo(amd64_x86 / amd64);
// 32bit - force 32 on 64bit mojo(x86 / x86_amd64)
// match mojo - os x86 is as above; os x64 mojo (x86 / amd64)
// Cross tools first if necessary, platform tools second, more generic tools
// later
if (force_requested_arch) {
if ("amd64".equals(mojoArchitecture) && !matchMojo) {
addPath(home, "VC/bin/amd64");
toolPathLinker = new File(home, "VC/bin/amd64").getAbsolutePath();
}
else {
addPath(home, "VC/bin");
toolPathLinker = new File(home, "VC/bin").getAbsolutePath();
}
}
else if (!osArchitecture.equals(mojoArchitecture) && !matchMojo) {
if (!addPath(home, "VC/bin/" + osArchitecture + "_" + mojoArchitecture)) {
throw new MojoExecutionException("Unable to find compiler for architecture " + mojoArchitecture + ".\n" +
new File(home, "VC/bin/" + osArchitecture + "_" + mojoArchitecture));
}
toolPathLinker = new File(home, "VC/bin/" + osArchitecture + "_" + mojoArchitecture).getAbsolutePath();
}
if (null == toolPathLinker) {
if ("amd64".equals(mojoArchitecture))
toolPathLinker = new File(home, "VC/bin/amd64").getAbsolutePath();
else
toolPathLinker = new File(home, "VC/bin").getAbsolutePath();
}
if ("amd64".equals(osArchitecture) && !matchMojo) {
addPath(home, "VC/bin/amd64");
}
else {
addPath(home, "VC/bin");
}
// clearing the path, add back the windows system folders
addPath(windowsHome, "System32");
addPath(windowsHome, "");
addPath(windowsHome, "System32/wbem");
}
private void initVisualStudio() throws MojoFailureException, MojoExecutionException {
mojo.getLog().debug(" -- Searching for usable VisualStudio ");
mojo.getLog().debug("Linker version is " + version);
if (version != null && version.trim().length() > 1) {
String internalVersion;
Pattern r = Pattern.compile("(\\d+)\\.*(\\d)");
Matcher matcher = r.matcher(version);
if (matcher.find()) {
internalVersion = matcher.group(1) + matcher.group(2);
version = matcher.group(1) + "." + matcher.group(2);
}
else {
throw new MojoExecutionException("msvc.version must be the internal version in the form 10.0 or 120");
}
if (home == null) {
final String commontToolsVar = System.getenv("VS" + internalVersion + "COMNTOOLS");
if (commontToolsVar != null && commontToolsVar.trim().length() > 0) {
final File commonToolsDirectory = new File(commontToolsVar);
if (commonToolsDirectory.exists()) {
home = commonToolsDirectory.getParentFile().getParentFile();
}
}
// TODO: else Registry might be more reliable but adds dependency to be
// able to acccess - HKLM\SOFTWARE\Microsoft\Visual Studio\Major.Minor:InstallDir
}
mojo.getLog()
.debug(String.format(" VisualStudio %1s (%2s) found %3s ", version, internalVersion, home));
}
else {
version = "";
for (final Entry<String, String> entry : System.getenv().entrySet()) {
final String key = entry.getKey();
final String value = entry.getValue();
final Pattern versionPattern = Pattern.compile("VS(\\d+)(\\d)COMNTOOLS");
final Matcher matcher = versionPattern.matcher(key);
if (matcher.matches()) {
final String version = matcher.group(1) + "." + matcher.group(2);
if (versionStringComparator.compare(version, version) > 0) {
final File commonToolsDirectory = new File(value);
if (commonToolsDirectory.exists()) {
this.version = version;
home = commonToolsDirectory.getParentFile().getParentFile();
mojo.getLog().debug(
String.format(" VisualStudio %1s (%2s) found %3s ", version,
matcher.group(1) + matcher.group(2), home));
}
}
}
}
if (version.length() == 0) {
final TextStream out = new StringTextStream();
final TextStream err = new StringTextStream();
final TextStream dbg = new StringTextStream();
NarUtil.runCommand("link", new String[] {
"/?"
}, null, null, out, err, dbg, null, true);
final Pattern p = Pattern.compile("(\\d+\\.\\d+)\\.\\d+(\\.\\d+)?");
final Matcher m = p.matcher(out.toString());
if (m.find()) {
version = m.group(1);
mojo.getLog().debug(
String.format(" VisualStudio Not found but link runs and reports version %1s (%2s)", version,
m.group(0)));
}
else {
throw new MojoExecutionException(
"msvc.version not specified and no VS<Version>COMNTOOLS environment variable can be found");
}
}
}
}
private final Comparator<String> versionStringComparator = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
DefaultArtifactVersion version1 = new DefaultArtifactVersion(o1);
DefaultArtifactVersion version2 = new DefaultArtifactVersion(o2);
return version1.compareTo(version2);
}
};
private final Comparator<File> versionComparator = new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
// will be sorted smallest first, so we need to invert the order of
// the objects
String firstDir = o2.getName(), secondDir = o1.getName();
if (firstDir.charAt(0) == 'v') { // remove 'v' and 'A' at the end
firstDir = firstDir.substring(1, firstDir.length() - 1);
secondDir = secondDir.substring(1, secondDir.length() - 1);
}
// impossible that two dirs are the same
String[] firstVersionString = firstDir.split("\\."), secondVersionString = secondDir.split("\\.");
int maxIdx = Math.min(firstVersionString.length, secondVersionString.length);
int deltaVer;
try {
for (int i = 0; i < maxIdx; i++)
if ((deltaVer = Integer.parseInt(firstVersionString[i]) - Integer.parseInt(secondVersionString[i])) != 0)
return deltaVer;
}
catch (NumberFormatException e) {
return firstDir.compareTo(secondDir);
}
if (firstVersionString.length > maxIdx) // 10.0.150 > 10.0
return 1;
else if (secondVersionString.length > maxIdx) // 10.0 < 10.0.150
return -1;
return 0; // impossible that they are the same
}
};
private boolean foundSDK = false;
private void initWindowsSdk() throws MojoExecutionException {
if (windowsSdkVersion != null && windowsSdkVersion.trim().equals(""))
windowsSdkVersion = null;
mojo.getLog().debug(" -- Searching for usable WindowSDK ");
// newer first: 10 -> 8.1 -> 8.0 -> 7.1 and look for libs specified
for (final File directory : Arrays.asList(
new File("C:/Program Files (x86)/Windows Kits"),
new File("C:/Program Files (x86)/Microsoft SDKs/Windows"),
new File("C:/Program Files/Windows Kits"),
new File("C:/Program Files/Microsoft SDKs/Windows") )) {
if (directory.exists()) {
final File[] kitDirectories = directory.listFiles();
Arrays.sort(kitDirectories, versionComparator);
if (kitDirectories != null) {
for (final File kitDirectory : kitDirectories) {
if (new File(kitDirectory, "Include").exists()) {
// legacy SDK
String kitVersion = kitDirectory.getName();
if (kitVersion.charAt(0) == 'v') {
kitVersion = kitVersion.substring(1);
}
if (windowsSdkVersion != null && compareVersion(kitVersion, windowsSdkVersion) != 0)
continue; // skip versions not identical to exact version
mojo.getLog()
.debug(String.format(" WindowSDK %1s found %2s", kitVersion, kitDirectory.getAbsolutePath()));
if (kitVersion.matches("\\d+\\.\\d+?[A-Z]?")) {
// windows <= 8.1
legacySDK(kitDirectory);
}
else if (kitVersion.matches("\\d+?")) {
// windows 10 SDK supports
addNewSDKLibraries(kitDirectory);
}
}
}
if (libsRequired.size() == 0) // need it here to break out of the outer loop
break;
}
}
}
if (!foundSDK)
{ // Search for SDK with lower versions
for (final File directory : Arrays.asList(
new File("C:/Program Files (x86)/Windows Kits"),
new File("C:/Program Files (x86)/Microsoft SDKs/Windows"),
new File("C:/Program Files/Windows Kits"),
new File("C:/Program Files/Microsoft SDKs/Windows") )) {
if (directory.exists()) {
final File[] kitDirectories = directory.listFiles();
Arrays.sort(kitDirectories, versionComparator);
if (kitDirectories != null) {
for (final File kitDirectory : kitDirectories) {
if (new File(kitDirectory, "Include").exists()) {
// legacy SDK
String kitVersion = kitDirectory.getName();
if (kitVersion.charAt(0) == 'v') {
kitVersion = kitVersion.substring(1);
}
if (windowsSdkVersion != null && compareVersion(kitVersion, windowsSdkVersion) > 0) {
continue; // skip versions higher than the previous version
}
mojo.getLog().debug(String.format(" WindowSDK %1s found %2s", kitVersion, kitDirectory.getAbsolutePath()));
if (kitVersion.matches("\\d+\\.\\d+?[A-Z]?")) {
// windows <= 8.1
legacySDK(kitDirectory);
}
else if (kitVersion.matches("\\d+?")) {
// windows 10 SDK supports
addNewSDKLibraries(kitDirectory);
}
}
}
if (libsRequired.size() == 0) // need it here to break out of the outer loop
break;
}
}
}
}
if (!foundSDK)
throw new MojoExecutionException("msvc.windowsSdkVersion not specified and versions cannot be found");
mojo.getLog().debug(String.format(" Using WindowSDK %1s found %2s", windowsSdkVersion, windowsSdkHome));
}
private void addNewSDKLibraries(final File kitDirectory) {
// multiple installs
List<File> kitVersionDirectories = Arrays.asList(new File(kitDirectory, "Include").listFiles());
Collections.sort(kitVersionDirectories, versionComparator);
ListIterator<File> kitVersionDirectoriesIt = kitVersionDirectories.listIterator();
File kitVersionDirectory = null;
while (kitVersionDirectoriesIt.hasNext() && (kitVersionDirectory = kitVersionDirectoriesIt.next()) != null) {
if (new File(kitVersionDirectory, "ucrt").exists()) {
break;
}
}
if (kitVersionDirectory != null) {
String version = kitVersionDirectory.getName();
mojo.getLog().debug(String.format(" Latest Win %1s KitDir at %2s", kitVersionDirectory.getName(), kitVersionDirectory.getAbsolutePath()));
// add the libraries found:
File includeDir = new File(kitDirectory, "Include/" + version);
File libDir = new File(kitDirectory, "Lib/" + version);
addSDKLibs(includeDir, libDir);
setKit(kitDirectory);
}
}
private void setKit(File home) {
if (!foundSDK) {
if (windowsSdkVersion == null) windowsSdkVersion = home.getName();
if (windowsSdkHome == null) windowsSdkHome = home;
foundSDK = true;
}
}
private void legacySDK(final File kitDirectory) {
File includeDir = new File(kitDirectory, "Include");
File libDir = new File(kitDirectory, "Lib");
if (includeDir.exists() && libDir.exists()){
File usableLibDir = null;
for (final File libSubDir : libDir.listFiles()) {
final File um = new File(libSubDir,"um");
if (um.exists()) usableLibDir = libSubDir;
}
if (usableLibDir == null)
usableLibDir = libDir.listFiles()[0];
addSDKLibs(includeDir, usableLibDir);
setKit(kitDirectory);
}
}
private void addSDKLibs(File includeDir, File libdir) {
final File[] libs = includeDir.listFiles();
for (final File libIncludeDir : libs) {
// <libName> <include path> <lib path>
if (libsRequired.remove(libIncludeDir.getName())) {
mojo.getLog().debug(String.format(" Using directory %1s for library %2s", libIncludeDir.getAbsolutePath(), libIncludeDir.getName()));
sdkIncludes.add(libIncludeDir);
sdkLibs.add(new File(libdir, libIncludeDir.getName()));
}
}
}
private void initWindowsSdk8() throws MojoExecutionException {
final String osArchitecture = NarUtil.getArchitecture(null);
//VS 2005 - The SDK files are included in the VS installation-
File VCINSTALLDIR = new File (home,"VC");
//File VSLibDir = new File(VCINSTALLDIR.getAbsolutePath()+File.separator+ "lib" , osArchitecture);
File PlatformSDKIncludeDir = new File(VCINSTALLDIR.getAbsolutePath()+ File.separator+ "PlatformSDK", "include");
File SDKIncludeDir = new File(VCINSTALLDIR.getAbsolutePath()+ File.separator+ "SDK"+ File.separator+ "v2.0", "include");
sdkIncludes.add(PlatformSDKIncludeDir);
sdkIncludes.add(SDKIncludeDir);
windowsSdkHome = home;
}
public void setMojo(final AbstractNarMojo mojo) throws MojoFailureException, MojoExecutionException {
if (this.mojo != mojo) {
this.mojo = mojo;
init();
}
}
@Override
public String toString() {
return "VS Home-"+ home + "\nSDKHome-" + windowsSdkHome;
}
public String getToolPath() {
return toolPathLinker;
}
public String getSDKToolPath() {
return toolPathWindowsSDK;
}
public void setToolPath(CompilerDef compilerDef, String name) {
if ("res".equals(name) || "mc".equals(name) || "idl".equals(name)) {
compilerDef.setToolPath(toolPathWindowsSDK);
}
else {
compilerDef.setToolPath(toolPathLinker);
}
}
public int compareVersion(Object o1, Object o2) {
String version1 = (String) o1;
String version2 = (String) o2;
VersionTokenizer tokenizer1 = new VersionTokenizer(version1);
VersionTokenizer tokenizer2 = new VersionTokenizer(version2);
int number1 = 0, number2 = 0;
String suffix1 = "", suffix2 = "";
while (tokenizer1.MoveNext()) {
if (!tokenizer2.MoveNext()) {
do {
number1 = tokenizer1.getNumber();
suffix1 = tokenizer1.getSuffix();
if (number1 != 0 || suffix1.length() != 0) {
// Version one is longer than number two, and non-zero
return 1;
}
} while (tokenizer1.MoveNext());
// Version one is longer than version two, but zero
return 0;
}
number1 = tokenizer1.getNumber();
suffix1 = tokenizer1.getSuffix();
number2 = tokenizer2.getNumber();
suffix2 = tokenizer2.getSuffix();
if (number1 < number2) {
// Number one is less than number two
return -1;
}
if (number1 > number2) {
// Number one is greater than number two
return 1;
}
boolean empty1 = suffix1.length() == 0;
boolean empty2 = suffix2.length() == 0;
if (empty1 && empty2)
continue; // No suffixes
if (empty1)
return 1; // First suffix is empty (1.2 > 1.2b)
if (empty2)
return -1; // Second suffix is empty (1.2a < 1.2)
// Lexical comparison of suffixes
int result = suffix1.compareTo(suffix2);
if (result != 0)
return result;
}
if (tokenizer2.MoveNext()) {
do {
number2 = tokenizer2.getNumber();
suffix2 = tokenizer2.getSuffix();
if (number2 != 0 || suffix2.length() != 0) {
// Version one is longer than version two, and non-zero
return -1;
}
} while (tokenizer2.MoveNext());
// Version two is longer than version one, but zero
return 0;
}
return 0;
}
// VersionTokenizer.java
class VersionTokenizer {
private final String _versionString;
private final int _length;
private int _position;
private int _number;
private String _suffix;
private boolean _hasValue;
public int getNumber() {
return _number;
}
public String getSuffix() {
return _suffix;
}
public boolean hasValue() {
return _hasValue;
}
public VersionTokenizer(String versionString) {
if (versionString == null)
throw new IllegalArgumentException("versionString is null");
_versionString = versionString;
_length = versionString.length();
}
public boolean MoveNext() {
_number = 0;
_suffix = "";
_hasValue = false;
// No more characters
if (_position >= _length)
return false;
_hasValue = true;
while (_position < _length) {
char c = _versionString.charAt(_position);
if (c < '0' || c > '9')
break;
_number = _number * 10 + (c - '0');
_position++;
}
int suffixStart = _position;
while (_position < _length) {
char c = _versionString.charAt(_position);
if (c == '.')
break;
_position++;
}
_suffix = _versionString.substring(suffixStart, _position);
if (_position < _length)
_position++;
return true;
}
}
}