/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.ivy.core.module.id;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.ivy.Ivy;
import org.apache.ivy.core.IvyContext;
import org.apache.ivy.core.IvyPatternHelper;
import org.apache.ivy.util.extendable.UnmodifiableExtendableItem;
/**
* Identifies a module in a particular version
*
* @see <a href="package-summary.html">org.apache.ivy.core.module.id</a>
*/
public class ModuleRevisionId extends UnmodifiableExtendableItem {
private static final String ENCODE_SEPARATOR = ModuleId.ENCODE_SEPARATOR;
private static final String ENCODE_PREFIX = "+";
private static final String NULL_ENCODE = "@#:NULL:#@";
static final String STRICT_CHARS_PATTERN = "[a-zA-Z0-9\\-/\\._+=]";
private static final String REV_STRICT_CHARS_PATTERN
= "[a-zA-Z0-9\\-/\\._+=,\\[\\]\\{\\}\\(\\):@]";
private static final Map/*<ModuleRevisionId, ModuleRevisionId>*/ CACHE = new WeakHashMap();
/**
* Pattern to use to matched mrid text representation.
* @see #parse(String)
*/
public static final Pattern MRID_PATTERN =
Pattern.compile(
"(" + STRICT_CHARS_PATTERN + "*)"
+ "#(" + STRICT_CHARS_PATTERN + "+)"
+ "(?:#(" + STRICT_CHARS_PATTERN + "+))?"
+ ";(" + REV_STRICT_CHARS_PATTERN + "+)");
/**
* Same as MRID_PATTERN but using non capturing groups, useful to build larger regexp
*/
public static final Pattern NON_CAPTURING_PATTERN =
Pattern.compile(
"(?:" + STRICT_CHARS_PATTERN + "*)"
+ "#(?:" + STRICT_CHARS_PATTERN + "+)"
+ "(?:#(?:" + STRICT_CHARS_PATTERN + "+))?"
+ ";(?:" + REV_STRICT_CHARS_PATTERN + "+)");
/**
* Parses a module revision id text representation and returns a new {@link ModuleRevisionId}
* instance corresponding to the parsed String.
* <p>
* The result is unspecified if the module doesn't respect strict name conventions.
* </p>
*
* @param mrid
* the text representation of the module (as returned by {@link #toString()}). Must
* not be <code>null</code>.
* @return a {@link ModuleRevisionId} corresponding to the given text representation
* @throws IllegalArgumentException
* if the given text representation does not match the {@link ModuleRevisionId} text
* representation rules.
*/
public static ModuleRevisionId parse(String mrid) {
Matcher m = MRID_PATTERN.matcher(mrid.trim());
if (!m.matches()) {
throw new IllegalArgumentException(
"module revision text representation do not match expected pattern."
+ " given mrid='" + mrid + "' expected form=" + MRID_PATTERN.pattern());
}
//CheckStyle:MagicNumber| OFF
return newInstance(m.group(1), m.group(2), m.group(3), m.group(4));
//CheckStyle:MagicNumber| ON
}
public static ModuleRevisionId newInstance(String organisation, String name, String revision) {
return intern(
new ModuleRevisionId(ModuleId.newInstance(organisation, name), revision));
}
public static ModuleRevisionId newInstance(String organisation, String name, String revision,
Map extraAttributes) {
return intern(
new ModuleRevisionId(ModuleId.newInstance(organisation, name),
revision, extraAttributes));
}
public static ModuleRevisionId newInstance(String organisation, String name, String branch,
String revision) {
return intern(
new ModuleRevisionId(ModuleId.newInstance(organisation, name),
branch, revision));
}
public static ModuleRevisionId newInstance(String organisation, String name, String branch,
String revision, Map extraAttributes) {
return intern(
new ModuleRevisionId(ModuleId.newInstance(organisation, name),
branch, revision, extraAttributes));
}
public static ModuleRevisionId newInstance(String organisation, String name, String branch,
String revision, Map extraAttributes, boolean replaceNullBranchWithDefault) {
return intern(
new ModuleRevisionId(ModuleId.newInstance(organisation, name),
branch, revision, extraAttributes, replaceNullBranchWithDefault));
}
public static ModuleRevisionId newInstance(ModuleRevisionId mrid, String rev) {
return intern(
new ModuleRevisionId(mrid.getModuleId(),
mrid.getBranch(), rev, mrid.getQualifiedExtraAttributes()));
}
public static ModuleRevisionId newInstance(ModuleRevisionId mrid, String branch, String rev) {
return intern(
new ModuleRevisionId(mrid.getModuleId(),
branch, rev, mrid.getQualifiedExtraAttributes()));
}
/**
* Returns an intern instance of the given ModuleRevisionId if any, or put the given
* ModuleRevisionId in a cache of intern instances and returns it.
* <p>
* This method should be called on ModuleRevisionId created with one of the constructor to
* decrease memory footprint.
* </p>
* <p>
* When using static newInstances methods, this method is already called.
* </p>
*
* @param moduleRevisionId
* the module revision id to intern
* @return an interned ModuleRevisionId
*/
public static ModuleRevisionId intern(ModuleRevisionId moduleRevisionId) {
ModuleRevisionId r = null;
synchronized (CACHE) {
WeakReference ref = (WeakReference) CACHE.get(moduleRevisionId);
if (ref != null) {
r = (ModuleRevisionId) ref.get();
}
if (r == null) {
r = moduleRevisionId;
CACHE.put(r, new WeakReference(r));
}
}
return r;
}
private final ModuleId moduleId;
private final String branch;
private final String revision;
private int hash;
// TODO: make these constructors private and use only static factory methods
public ModuleRevisionId(ModuleId moduleId, String revision) {
this(moduleId, null, revision, null);
}
public ModuleRevisionId(ModuleId moduleId, String branch, String revision) {
this(moduleId, branch, revision, null);
}
private ModuleRevisionId(ModuleId moduleId, String revision, Map extraAttributes) {
this(moduleId, null, revision, extraAttributes);
}
private ModuleRevisionId(ModuleId moduleId, String branch, String revision,
Map extraAttributes) {
this(moduleId, branch, revision, extraAttributes, true);
}
private ModuleRevisionId(ModuleId moduleId, String branch, String revision,
Map extraAttributes, boolean replaceNullBranchWithDefault) {
super(null, extraAttributes);
this.moduleId = moduleId;
IvyContext context = IvyContext.getContext();
this.branch = (replaceNullBranchWithDefault && branch == null)
// we test if there's already an Ivy instance loaded, to avoid loading a default one
// just to get the default branch
? (context.peekIvy() == null ? null : context.getSettings().getDefaultBranch(moduleId))
: branch;
this.revision = revision == null ? Ivy.getWorkingRevision() : normalizeRevision(revision);
setStandardAttribute(IvyPatternHelper.ORGANISATION_KEY, this.moduleId.getOrganisation());
setStandardAttribute(IvyPatternHelper.MODULE_KEY, this.moduleId.getName());
setStandardAttribute(IvyPatternHelper.BRANCH_KEY, this.branch);
setStandardAttribute(IvyPatternHelper.REVISION_KEY, this.revision);
}
public ModuleId getModuleId() {
return moduleId;
}
public String getName() {
return getModuleId().getName();
}
public String getOrganisation() {
return getModuleId().getOrganisation();
}
public String getRevision() {
return revision;
}
public boolean equals(Object obj) {
if (!(obj instanceof ModuleRevisionId)) {
return false;
}
ModuleRevisionId other = (ModuleRevisionId) obj;
if (!other.getRevision().equals(getRevision())) {
return false;
} else if (other.getBranch() == null && getBranch() != null) {
return false;
} else if (other.getBranch() != null && !other.getBranch().equals(getBranch())) {
return false;
} else if (!other.getModuleId().equals(getModuleId())) {
return false;
} else {
return other.getQualifiedExtraAttributes().equals(getQualifiedExtraAttributes());
}
}
public int hashCode() {
if (hash == 0) {
//CheckStyle:MagicNumber| OFF
hash = 31;
hash = hash * 13 + (getBranch() == null ? 0 : getBranch().hashCode());
hash = hash * 13 + getRevision().hashCode();
hash = hash * 13 + getModuleId().hashCode();
hash = hash * 13 + getQualifiedExtraAttributes().hashCode();
//CheckStyle:MagicNumber| ON
}
return hash;
}
public String toString() {
return moduleId
+ (branch == null || branch.length() == 0 ? "" : "#" + branch) + ";"
+ (revision == null ? "NONE" : revision);
}
public String encodeToString() {
StringBuffer buf = new StringBuffer();
Map attributes = new HashMap(getAttributes());
attributes.keySet().removeAll(getExtraAttributes().keySet());
attributes.putAll(getQualifiedExtraAttributes());
for (Iterator iter = attributes.keySet().iterator(); iter.hasNext();) {
String attName = (String) iter.next();
String value = (String) attributes.get(attName);
value = value == null ? NULL_ENCODE : value;
buf.append(ENCODE_PREFIX).append(attName).append(ENCODE_SEPARATOR)
.append(ENCODE_PREFIX).append(value).append(ENCODE_SEPARATOR);
}
return buf.toString();
}
public static ModuleRevisionId decode(String encoded) {
String[] parts = encoded.split(ENCODE_SEPARATOR);
if (parts.length % 2 != 0) {
throw new IllegalArgumentException("badly encoded module revision id: '" + encoded
+ "'");
}
Map attributes = new HashMap();
for (int i = 0; i < parts.length; i += 2) {
String attName = parts[i];
if (!attName.startsWith(ENCODE_PREFIX)) {
throw new IllegalArgumentException("badly encoded module revision id: '" + encoded
+ "': " + attName + " doesn't start with " + ENCODE_PREFIX);
} else {
attName = attName.substring(1);
}
String attValue = parts[i + 1];
if (!attValue.startsWith(ENCODE_PREFIX)) {
throw new IllegalArgumentException("badly encoded module revision id: '" + encoded
+ "': " + attValue + " doesn't start with " + ENCODE_PREFIX);
} else {
attValue = attValue.substring(1);
}
if (NULL_ENCODE.equals(attValue)) {
attValue = null;
}
attributes.put(attName, attValue);
}
String org = (String) attributes.remove(IvyPatternHelper.ORGANISATION_KEY);
String mod = (String) attributes.remove(IvyPatternHelper.MODULE_KEY);
String rev = (String) attributes.remove(IvyPatternHelper.REVISION_KEY);
String branch = (String) attributes.remove(IvyPatternHelper.BRANCH_KEY);
if (org == null) {
throw new IllegalArgumentException("badly encoded module revision id: '" + encoded
+ "': no organisation");
}
if (mod == null) {
throw new IllegalArgumentException("badly encoded module revision id: '" + encoded
+ "': no module name");
}
if (rev == null) {
throw new IllegalArgumentException("badly encoded module revision id: '" + encoded
+ "': no revision");
}
return newInstance(org, mod, branch, rev, attributes);
}
public String getBranch() {
return branch;
}
/**
* [revision] is a valid revision in maven. This method strips the '[' and ']'
* characters. Cfr. http://docs.codehaus.org/x/IGU
*/
private static String normalizeRevision(String revision) {
if (revision.startsWith("[") && revision.endsWith("]") && revision.indexOf(',') == -1) {
if (IvyPatternHelper.getTokenString(IvyPatternHelper.REVISION_KEY).equals(revision)) {
// this is the case when listing dynamic revions
return revision;
}
return revision.substring(1, revision.length() - 1);
} else {
return revision;
}
}
}