001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.ivy.core.module.id;
019:
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.Map;
023: import java.util.WeakHashMap;
024: import java.util.regex.Matcher;
025: import java.util.regex.Pattern;
026:
027: import org.apache.ivy.Ivy;
028: import org.apache.ivy.core.IvyContext;
029: import org.apache.ivy.core.IvyPatternHelper;
030: import org.apache.ivy.util.extendable.UnmodifiableExtendableItem;
031:
032: /**
033: * Identifies a module in a particular version
034: *
035: * @see <a href="package-summary.html">org.apache.ivy.core.module.id</a>
036: */
037: public class ModuleRevisionId extends UnmodifiableExtendableItem {
038: private static final String ENCODE_SEPARATOR = ModuleId.ENCODE_SEPARATOR;
039:
040: private static final String ENCODE_PREFIX = "+";
041:
042: private static final String NULL_ENCODE = "@#:NULL:#@";
043:
044: static final String STRICT_CHARS_PATTERN = "[a-zA-Z0-9\\-/\\._+=]";
045: private static final String REV_STRICT_CHARS_PATTERN = "[a-zA-Z0-9\\-/\\._+=,\\[\\]\\{\\}\\(\\):@]";
046:
047: private static final Map/*<ModuleRevisionId, ModuleRevisionId>*/CACHE = new WeakHashMap();
048:
049: /**
050: * Pattern to use to matched mrid text representation.
051: * @see #parse(String)
052: */
053: public static final Pattern MRID_PATTERN = Pattern.compile("("
054: + STRICT_CHARS_PATTERN + "*)" + "#(" + STRICT_CHARS_PATTERN
055: + "+)" + "(?:#(" + STRICT_CHARS_PATTERN + "+))?" + ";("
056: + REV_STRICT_CHARS_PATTERN + "+)");
057: /**
058: * Same as MRID_PATTERN but using non capturing groups, useful to build larger regexp
059: */
060: public static final Pattern NON_CAPTURING_PATTERN = Pattern
061: .compile("(?:" + STRICT_CHARS_PATTERN + "*)" + "#(?:"
062: + STRICT_CHARS_PATTERN + "+)" + "(?:#(?:"
063: + STRICT_CHARS_PATTERN + "+))?" + ";(?:"
064: + REV_STRICT_CHARS_PATTERN + "+)");
065:
066: /**
067: * Parses a module revision id text representation and returns a new {@link ModuleRevisionId}
068: * instance corresponding to the parsed String.
069: * <p>
070: * The result is unspecified if the module doesn't respect strict name conventions.
071: * </p>
072: *
073: * @param mrid
074: * the text representation of the module (as returned by {@link #toString()}). Must
075: * not be <code>null</code>.
076: * @return a {@link ModuleRevisionId} corresponding to the given text representation
077: * @throws IllegalArgumentException
078: * if the given text representation does not match the {@link ModuleRevisionId} text
079: * representation rules.
080: */
081: public static ModuleRevisionId parse(String mrid) {
082: Matcher m = MRID_PATTERN.matcher(mrid.trim());
083: if (!m.matches()) {
084: throw new IllegalArgumentException(
085: "module revision text representation do not match expected pattern."
086: + " given mrid='" + mrid
087: + "' expected form="
088: + MRID_PATTERN.pattern());
089: }
090:
091: //CheckStyle:MagicNumber| OFF
092: return newInstance(m.group(1), m.group(2), m.group(3), m
093: .group(4));
094: //CheckStyle:MagicNumber| ON
095: }
096:
097: public static ModuleRevisionId newInstance(String organisation,
098: String name, String revision) {
099: return intern(new ModuleRevisionId(ModuleId.newInstance(
100: organisation, name), revision));
101: }
102:
103: public static ModuleRevisionId newInstance(String organisation,
104: String name, String revision, Map extraAttributes) {
105: return intern(new ModuleRevisionId(ModuleId.newInstance(
106: organisation, name), revision, extraAttributes));
107: }
108:
109: public static ModuleRevisionId newInstance(String organisation,
110: String name, String branch, String revision) {
111: return intern(new ModuleRevisionId(ModuleId.newInstance(
112: organisation, name), branch, revision));
113: }
114:
115: public static ModuleRevisionId newInstance(String organisation,
116: String name, String branch, String revision,
117: Map extraAttributes) {
118: return intern(new ModuleRevisionId(ModuleId.newInstance(
119: organisation, name), branch, revision, extraAttributes));
120: }
121:
122: public static ModuleRevisionId newInstance(ModuleRevisionId mrid,
123: String rev) {
124: return intern(new ModuleRevisionId(mrid.getModuleId(), mrid
125: .getBranch(), rev, mrid.getExtraAttributes()));
126: }
127:
128: /**
129: * Returns an intern instance of the given ModuleRevisionId if any, or put the given
130: * ModuleRevisionId in a cache of intern instances and returns it.
131: * <p>
132: * This method should be called on ModuleRevisionId created with one of the constructor to
133: * decrease memory footprint.
134: * </p>
135: * <p>
136: * When using static newInstances methods, this method is already called.
137: * </p>
138: *
139: * @param moduleRevisionId
140: * the module revision id to intern
141: * @return an interned ModuleRevisionId
142: */
143: public static ModuleRevisionId intern(
144: ModuleRevisionId moduleRevisionId) {
145: ModuleRevisionId r = (ModuleRevisionId) CACHE
146: .get(moduleRevisionId);
147: if (r == null) {
148: r = moduleRevisionId;
149: CACHE.put(r, r);
150:
151: }
152: return r;
153: }
154:
155: private final ModuleId moduleId;
156:
157: private final String branch;
158:
159: private final String revision;
160:
161: private int hash;
162:
163: // TODO: make these constructors private and use only static factory methods
164:
165: public ModuleRevisionId(ModuleId moduleId, String revision) {
166: this (moduleId, null, revision, null);
167: }
168:
169: public ModuleRevisionId(ModuleId moduleId, String branch,
170: String revision) {
171: this (moduleId, branch, revision, null);
172: }
173:
174: private ModuleRevisionId(ModuleId moduleId, String revision,
175: Map extraAttributes) {
176: this (moduleId, null, revision, extraAttributes);
177: }
178:
179: private ModuleRevisionId(ModuleId moduleId, String branch,
180: String revision, Map extraAttributes) {
181: super (null, extraAttributes);
182: this .moduleId = moduleId;
183: IvyContext context = IvyContext.getContext();
184: this .branch = branch == null
185: // we test if there's already an Ivy instance loaded, to avoid loading a default one
186: // just to get the default branch
187: ? (context.peekIvy() == null ? null : context.getSettings()
188: .getDefaultBranch(moduleId))
189: : branch;
190: this .revision = revision == null ? Ivy.getWorkingRevision()
191: : revision;
192: setStandardAttribute(IvyPatternHelper.ORGANISATION_KEY,
193: this .moduleId.getOrganisation());
194: setStandardAttribute(IvyPatternHelper.MODULE_KEY, this .moduleId
195: .getName());
196: setStandardAttribute(IvyPatternHelper.BRANCH_KEY, this .branch);
197: setStandardAttribute(IvyPatternHelper.REVISION_KEY,
198: this .revision);
199: }
200:
201: public ModuleId getModuleId() {
202: return moduleId;
203: }
204:
205: public String getName() {
206: return getModuleId().getName();
207: }
208:
209: public String getOrganisation() {
210: return getModuleId().getOrganisation();
211: }
212:
213: public String getRevision() {
214: return revision;
215: }
216:
217: public boolean equals(Object obj) {
218: if (!(obj instanceof ModuleRevisionId)) {
219: return false;
220: }
221: ModuleRevisionId other = (ModuleRevisionId) obj;
222:
223: if (!other.getRevision().equals(getRevision())) {
224: return false;
225: } else if (other.getBranch() == null && getBranch() != null) {
226: return false;
227: } else if (other.getBranch() != null
228: && !other.getBranch().equals(getBranch())) {
229: return false;
230: } else if (!other.getModuleId().equals(getModuleId())) {
231: return false;
232: } else {
233: return other.getExtraAttributes().equals(
234: getExtraAttributes());
235: }
236: }
237:
238: public int hashCode() {
239: if (hash == 0) {
240: //CheckStyle:MagicNumber| OFF
241: hash = 31;
242: hash = hash
243: * 13
244: + (getBranch() == null ? 0 : getBranch().hashCode());
245: hash = hash * 13 + getRevision().hashCode();
246: hash = hash * 13 + getModuleId().hashCode();
247: hash = hash * 13 + getAttributes().hashCode();
248: //CheckStyle:MagicNumber| ON
249: }
250: return hash;
251: }
252:
253: public String toString() {
254: return moduleId
255: + (branch == null || branch.length() == 0 ? "" : "#"
256: + branch) + ";"
257: + (revision == null ? "NONE" : revision);
258: }
259:
260: public String encodeToString() {
261: StringBuffer buf = new StringBuffer();
262: Map attributes = getAttributes();
263: for (Iterator iter = attributes.keySet().iterator(); iter
264: .hasNext();) {
265: String attName = (String) iter.next();
266: String value = (String) attributes.get(attName);
267: value = value == null ? NULL_ENCODE : value;
268: buf.append(ENCODE_PREFIX).append(attName).append(
269: ENCODE_SEPARATOR).append(ENCODE_PREFIX).append(
270: value).append(ENCODE_SEPARATOR);
271: }
272: return buf.toString();
273: }
274:
275: public static ModuleRevisionId decode(String encoded) {
276: String[] parts = encoded.split(ENCODE_SEPARATOR);
277: if (parts.length % 2 != 0) {
278: throw new IllegalArgumentException(
279: "badly encoded module revision id: '" + encoded
280: + "'");
281: }
282: Map attributes = new HashMap();
283: for (int i = 0; i < parts.length; i += 2) {
284: String attName = parts[i];
285: if (!attName.startsWith(ENCODE_PREFIX)) {
286: throw new IllegalArgumentException(
287: "badly encoded module revision id: '" + encoded
288: + "': " + attName
289: + " doesn't start with "
290: + ENCODE_PREFIX);
291: } else {
292: attName = attName.substring(1);
293: }
294: String attValue = parts[i + 1];
295: if (!attValue.startsWith(ENCODE_PREFIX)) {
296: throw new IllegalArgumentException(
297: "badly encoded module revision id: '" + encoded
298: + "': " + attValue
299: + " doesn't start with "
300: + ENCODE_PREFIX);
301: } else {
302: attValue = attValue.substring(1);
303: }
304: if (NULL_ENCODE.equals(attValue)) {
305: attValue = null;
306: }
307: attributes.put(attName, attValue);
308: }
309: String org = (String) attributes
310: .remove(IvyPatternHelper.ORGANISATION_KEY);
311: String mod = (String) attributes
312: .remove(IvyPatternHelper.MODULE_KEY);
313: String rev = (String) attributes
314: .remove(IvyPatternHelper.REVISION_KEY);
315: String branch = (String) attributes
316: .remove(IvyPatternHelper.BRANCH_KEY);
317: if (org == null) {
318: throw new IllegalArgumentException(
319: "badly encoded module revision id: '" + encoded
320: + "': no organisation");
321: }
322: if (mod == null) {
323: throw new IllegalArgumentException(
324: "badly encoded module revision id: '" + encoded
325: + "': no module name");
326: }
327: if (rev == null) {
328: throw new IllegalArgumentException(
329: "badly encoded module revision id: '" + encoded
330: + "': no revision");
331: }
332: return newInstance(org, mod, branch, rev, attributes);
333: }
334:
335: public String getBranch() {
336: return branch;
337: }
338: }
|