/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. * * Contributor(s): * * Portions Copyrighted 2009 Sun Microsystems, Inc. */ package org.netbeans.modules.ruby.rubyproject; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.netbeans.modules.ruby.platform.Util; import org.netbeans.modules.ruby.platform.gems.Gem; import org.openide.util.Parameters; /** * Represents a gem requirement in a Ruby/Rails application. * * @author Erno Mononen */ public final class GemRequirement implements Comparable<GemRequirement>{ /** * Pattern for parsing Bundler output */ private static final Pattern BUNDLER = Pattern.compile("\\s+\\*\\s(\\S+)\\s\\((\\S+)\\)\\s*"); /** * Patterns for parsing requirement info from 'rake gems' output. */ private static final Pattern STATUS = Pattern.compile("\\s*-\\s*\\[(.*)\\].*"); private static final Pattern NAME = Pattern.compile("\\s*-\\s*\\[.*\\]\\s(\\S*).*"); private static final Pattern VERSION = Pattern.compile(".*\\s+(\\d+[\\.\\d]*)\\s*"); private static final Pattern OPERATOR = Pattern.compile(".*\\s(\\S+)\\s\\d+.*"); enum Status { INSTALLED("I"), NOT_INSTALLED(" "), FROZEN("F"), FRAMEWORK("R"), UNKNOWN("unknown"); private final String code; private Status(String code) { this.code = code; } static Status statusFor(String status) { for (Status each : values()) { if (each.code.equals(status)) { return each; } } return UNKNOWN; } } private final Status status; /** * The name of the required gem. */ private final String name; /** * The version required. */ private final String version; /** * The operator for the version requirement; one of the following: * <code>=, !=, >=, <=, >, <, ~></code>. */ private final String operator; public static GemRequirement fromString(String gemRequirement) { String[] parts = gemRequirement.split(" "); if (parts.length == 1) { // contains just the name return new GemRequirement(gemRequirement, "", "", Status.UNKNOWN); } assert parts.length == 3 : "Invalid requirement " + gemRequirement; return new GemRequirement(parts[0], parts[2], parts[1], Status.UNKNOWN); } GemRequirement(String name, String version, String operator, Status status) { Parameters.notNull("name", name); Parameters.notNull("status", status); this.name = name; this.version = version; this.operator = operator; this.status = status; } public static GemRequirement forGem(Gem gem) { return new GemRequirement(gem.getName(), null, null, Status.INSTALLED); } public static String[] getOperators() { return new String[]{">=", ">", "=", "<=", "<", "!=", "~>"}; } Status getStatus() { return status; } /** * @see #name */ public String getName() { return name; } public String getNameWithVersion() { if (version == null || "".equals(version)) { return name; } return name + "-" + version; } /** * @see #operator */ public String getOperator() { return operator; } /** * @see #version */ public String getVersion() { return version; } /** * Gets the version requirement, e.g. <code>">= 1.2.3"</code>. * * @return the version requirement; returns an empty string if it isn't specified. */ public String getVersionRequirement() { if (version == null || operator == null) { return ""; } return operator + " " + version; } /** * Checks whether the given <code>gemVersion</code> satisfies the * version requirement of <codet>this</code>. * * @param gemVersion * @return */ public boolean satisfiedBy(String gemVersion) { if (this.version == null || "".equals(this.version)) { return true; } if ("=".equals(operator) && version.equals(gemVersion)) { return true; } if (">".equals(operator)) { return Util.compareVersions(gemVersion, version) > 0; } if (">=".equals(operator)) { return Util.compareVersions(gemVersion, version) >= 0; } if ("<".equals(operator)) { return Util.compareVersions(gemVersion, version) < 0; } if ("<=".equals(operator)) { return Util.compareVersions(gemVersion, version) < 1; } if ("!=".equals(operator)) { return Util.compareVersions(gemVersion, version) != 0; } if ("~>".equals(operator)) { return Util.compareVersions(gemVersion, version) >= 0 && Util.compareVersions(gemVersion, bumbVersion(version)) < 0; } return false; } /** * Increments the given version by one (minor or major if no minor version * is present). * @param version * @return */ private static String bumbVersion(String version) { String[] ints = version.split("\\."); try { if (ints.length == 1) { int major = Integer.parseInt(ints[0]); return "" + major++; } else if (ints.length > 1) { int minor = Integer.parseInt(ints[1]); ints[1] = "" + (minor + 1); String res = ""; for (int i = 0; i < ints.length; i++) { if (i == 0) { res += ints[i]; } else { res += "." + ints[i]; } } return res; } } catch (NumberFormatException numberFormatException) { //XXX not sure what do here return version; } return version; } /** * Parser a <code>GemRequirement</code> from the given line. The expected * format of the line is the same as what either <code>bundle show</code> or * <code>rake gems</code> outputs. * * @param line the line to parse. * @return the parsed requirement or <code>null</code>. */ public static GemRequirement parse(String line) { Matcher bundlerMatcher = BUNDLER.matcher(line); if (bundlerMatcher.matches()) { return new GemRequirement(bundlerMatcher.group(1), bundlerMatcher.group(2), "=", Status.UNKNOWN); } Matcher statusMatcher = STATUS.matcher(line); Matcher nameMatcher = NAME.matcher(line); Matcher versionMatcher = VERSION.matcher(line); Matcher operatorMatcher = OPERATOR.matcher(line); String name, version, operator, status = null; if (nameMatcher.matches()) { name = nameMatcher.group(1); version = versionMatcher.matches() ? versionMatcher.group(1) : ""; operator = operatorMatcher.matches() ? operatorMatcher.group(1) : ""; status = statusMatcher.matches() ? statusMatcher.group(1) : ""; return new GemRequirement(name, version, operator, Status.statusFor(status)); } return null; } /** * @return this in a string format, e.g. <code>some-gem >= 1.2.3</code>. */ String asString() { StringBuilder result = new StringBuilder(name); if (!isEmpty(operator) && !isEmpty(version)) { result.append(" " + operator); result.append(" " + version); } return result.toString(); } private static boolean isEmpty(String str) { return str == null || "".equals(str.trim()); } @Override public String toString() { return GemRequirement.class.getName() + "[" + name + " " + operator + " " + version + " " + status + "]"; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final GemRequirement other = (GemRequirement) obj; if (this.status != other.status && (this.status == null || !this.status.equals(other.status))) { return false; } if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { return false; } if ((this.version == null) ? (other.version != null) : !this.version.equals(other.version)) { return false; } if ((this.operator == null) ? (other.operator != null) : !this.operator.equals(other.operator)) { return false; } return true; } @Override public int hashCode() { int hash = 5; hash = 67 * hash + (this.status != null ? this.status.hashCode() : 0); hash = 67 * hash + (this.name != null ? this.name.hashCode() : 0); hash = 67 * hash + (this.version != null ? this.version.hashCode() : 0); hash = 67 * hash + (this.operator != null ? this.operator.hashCode() : 0); return hash; } public int compareTo(GemRequirement o) { int result = this.name.compareTo(o.name); if (result != 0) { return result; } if (this.version != null && o.version != null) { return this.version.compareTo(o.version); } if (this.version != null) { return 1; } else { return -1; } } }