| /******************************************************************************* |
| * Copyright (c) 2009 Cloudsmith Inc. and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Cloudsmith Inc. - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.equinox.internal.p2.metadata; |
| |
| import java.io.Serializable; |
| import java.util.*; |
| import org.eclipse.equinox.internal.p2.metadata.VersionFormatParser.Fragment; |
| import org.eclipse.equinox.p2.metadata.*; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * <p>The VersionFormat represents the Omni Version Format in compiled form. It |
| * is also a parser for versions of that format.</p> |
| * <p>An instance of VersionFormat is immutable and thus thread safe. The parser |
| * does not maintain any state.</p> |
| * |
| * @Immutable |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class VersionFormat implements IVersionFormat, Serializable { |
| |
| /** |
| * The string representation of the Omni Version format used for parsing OSGi versions. |
| */ |
| public static final String OSGI_FORMAT_STRING = "n[.n=0;[.n=0;[.S='';=[A-Za-z0-9_-];]]]"; //$NON-NLS-1$ |
| |
| /** |
| * The string representation of the Omni Version format used for parsing raw versions. |
| */ |
| public static final String RAW_FORMAT_STRING = "r(.r)*p?"; //$NON-NLS-1$ |
| |
| private static final long serialVersionUID = -5689435955091405520L; |
| |
| private static class StateInfo { |
| Fragment fragment; |
| int position; |
| int segmentCount; |
| |
| StateInfo(int position, int segmentCount, Fragment fragment) { |
| this.fragment = fragment; |
| this.position = position; |
| this.segmentCount = segmentCount; |
| } |
| } |
| |
| static class TreeInfo extends ArrayList<StateInfo> { |
| private static final long serialVersionUID = 4474591345244587260L; |
| |
| private Comparable<?> padValue; |
| private int top; |
| |
| TreeInfo(Fragment frag, int pos) { |
| add(new StateInfo(pos, 0, frag)); |
| top = 0; |
| } |
| |
| Comparable<?> getPadValue() { |
| return padValue; |
| } |
| |
| int getPosition() { |
| return get(top).position; |
| } |
| |
| void popState(List<Comparable<?>> segments, Fragment frag) { |
| int idx = top; |
| while (idx > 0) { |
| StateInfo si = get(idx); |
| if (si.fragment == frag) { |
| int nsegs = segments.size(); |
| int segMax = si.segmentCount; |
| while (nsegs > segMax) |
| segments.remove(--nsegs); |
| top = idx - 1; |
| break; |
| } |
| } |
| } |
| |
| void pushState(int segCount, Fragment fragment) { |
| int pos = get(top).position; |
| if (++top == size()) |
| add(new StateInfo(pos, segCount, fragment)); |
| else { |
| StateInfo si = get(top); |
| si.fragment = fragment; |
| si.position = pos; |
| si.segmentCount = segCount; |
| } |
| } |
| |
| void setPadValue(Comparable<?> pad) { |
| padValue = pad; |
| } |
| |
| void setPosition(int pos) { |
| get(top).position = pos; |
| } |
| } |
| |
| private static final Map<String, VersionFormat> formatCache = Collections.synchronizedMap(new HashMap<String, VersionFormat>()); |
| |
| /** |
| * The predefined OSGi format that is used when parsing OSGi |
| * versions. |
| */ |
| public static final VersionFormat OSGI_FORMAT; |
| |
| /** |
| * The predefined OSGi format that is used when parsing raw |
| * versions. |
| */ |
| public static final VersionFormat RAW_FORMAT; |
| |
| static { |
| try { |
| VersionFormatParser parser = new VersionFormatParser(); |
| OSGI_FORMAT = new VersionFormat(parser.compile(OSGI_FORMAT_STRING, 0, OSGI_FORMAT_STRING.length())); |
| formatCache.put(OSGI_FORMAT_STRING, OSGI_FORMAT); |
| RAW_FORMAT = new RawFormat(parser.compile(RAW_FORMAT_STRING, 0, RAW_FORMAT_STRING.length())); |
| formatCache.put(RAW_FORMAT_STRING, RAW_FORMAT); |
| } catch (VersionFormatException e) { |
| // If this happens, something is wrong with the actual |
| // implementation of the FormatCompiler. |
| // |
| throw new ExceptionInInitializerError(e); |
| } |
| } |
| |
| /** |
| * Compile a version format string into a compiled format. This method is |
| * shorthand for:<pre>CompiledFormat.compile(format, 0, format.length())</pre>. |
| * |
| * @param format The format to compile. |
| * @return The compiled format |
| * @throws VersionFormatException If the format could not be compiled |
| */ |
| public static IVersionFormat compile(String format) throws VersionFormatException { |
| return compile(format, 0, format.length()); |
| } |
| |
| /** |
| * Compile a version format string into a compiled format. The parsing starts |
| * at position start and ends at position end. The returned format is cached so |
| * subsequent calls to this method using the same format string will yield the |
| * same compiled format instance. |
| * |
| * @param format The format string to compile. |
| * @param start Start position in the format string |
| * @param end End position in the format string |
| * @return The compiled format |
| * @throws VersionFormatException If the format could not be compiled |
| */ |
| public static VersionFormat compile(String format, int start, int end) throws VersionFormatException { |
| String fmtString = format.substring(start, end).intern(); |
| synchronized (fmtString) { |
| VersionFormat fmt = formatCache.get(fmtString); |
| if (fmt == null) { |
| VersionFormatParser parser = new VersionFormatParser(); |
| fmt = new VersionFormat(parser.compile(format, start, end)); |
| formatCache.put(fmtString, fmt); |
| } |
| return fmt; |
| } |
| } |
| |
| /** |
| * Parse a version string using the {@link #RAW_FORMAT} parser. |
| * |
| * @param version The version to parse. |
| * @param originalFormat The original format to assign to the created version. Can be <code>null</code>. |
| * @param original The original version string to assign to the created version. Can be <code>null</code>. |
| * @return A created version |
| * @throws IllegalArgumentException If the version string could not be parsed. |
| */ |
| public static BasicVersion parseRaw(String version, IVersionFormat originalFormat, String original) { |
| List<Comparable<?>> vector = RAW_FORMAT.parse(version, 0, version.length()); |
| return (originalFormat == OSGI_FORMAT) ? OSGiVersion.fromVector(vector) : OmniVersion.fromVector(vector, originalFormat, original); |
| } |
| |
| static void rawToString(StringBuffer sb, boolean forRange, Comparable<?> e) { |
| if (e instanceof String) { |
| writeQuotedString(sb, forRange, (String) e, '\'', 0, false); |
| } else if (e instanceof VersionVector) { |
| sb.append('<'); |
| ((VersionVector) e).toString(sb, forRange); |
| sb.append('>'); |
| } else |
| sb.append(e); |
| } |
| |
| /** |
| * Write a string within quotes. If the string is found to contain the quote, an attempt is made |
| * to flip quote character (single quote becomes double quote and vice versa). A string that contains |
| * both will be written as several adjacent quoted strings so that each string is quoted with a |
| * quote character that it does not contain. |
| * @param sb The buffer that will receive the string |
| * @param rangeSafe Set to <code>true</code> if the resulting string will be used in a range string |
| * and hence need to escape the range delimiter characters |
| * @param s The string to be written |
| * @param quote The quote character to start with. Must be the single or double quote character. |
| * @param startPos The start position |
| * @param didFlip True if the call is recursive and thus, cannot switch quotes in the first string. |
| */ |
| private static void writeQuotedString(StringBuffer sb, boolean rangeSafe, String s, char quote, int startPos, boolean didFlip) { |
| int quotePos = sb.length(); |
| sb.append(quote); |
| boolean otherSeen = false; |
| int top = s.length(); |
| for (int idx = startPos; idx < top; ++idx) { |
| char c = s.charAt(idx); |
| if (c == '\'' || c == '"') { |
| if (c == quote) { |
| char otherQuote = quote == '\'' ? '"' : '\''; |
| if (didFlip || otherSeen) { |
| // We can only flip once |
| sb.append(quote); |
| writeQuotedString(sb, rangeSafe, s, otherQuote, idx, true); |
| return; |
| } |
| quote = otherQuote; |
| sb.setCharAt(quotePos, quote); |
| didFlip = true; |
| } else |
| otherSeen = true; |
| } |
| if (rangeSafe && (c == '\\' || c == '[' || c == '(' || c == ']' || c == ')' || c == ',' || c <= ' ')) |
| sb.append('\\'); |
| sb.append(c); |
| } |
| sb.append(quote); |
| } |
| |
| private String fmtString; |
| |
| private final Fragment topFragment; |
| |
| VersionFormat(Fragment topFragment) { |
| this.topFragment = topFragment; |
| } |
| |
| TreeInfo createInfo(int start) { |
| return new TreeInfo(topFragment, start); |
| } |
| |
| public boolean equals(Object o) { |
| return this == o || o instanceof VersionFormat && toString().equals(o.toString()); |
| } |
| |
| public int hashCode() { |
| return 11 * toString().hashCode(); |
| } |
| |
| public Version parse(String version) { |
| List<Comparable<?>> vector = parse(version, 0, version.length()); |
| return (this == OSGI_FORMAT) ? OSGiVersion.fromVector(vector) : OmniVersion.fromVector(vector, this, version); |
| } |
| |
| List<Comparable<?>> parse(String version, int start, int maxPos) { |
| if (start == maxPos) |
| throw new IllegalArgumentException(NLS.bind(Messages.format_0_unable_to_parse_empty_version, this, version.substring(start, maxPos))); |
| TreeInfo info = new TreeInfo(topFragment, start); |
| ArrayList<Comparable<?>> entries = new ArrayList<Comparable<?>>(5); |
| if (!(topFragment.parse(entries, version, maxPos, info) && info.getPosition() == maxPos)) |
| throw new IllegalArgumentException(NLS.bind(Messages.format_0_unable_to_parse_1, this, version.substring(start, maxPos))); |
| entries.add(VersionParser.removeRedundantTrail(entries, info.getPadValue())); |
| return entries; |
| } |
| |
| // Preserve cache during deserialization |
| private Object readResolve() { |
| synchronized (formatCache) { |
| String string = toString(); |
| string = string.substring(7, string.length() - 1); // Strip of "format(" and ")" |
| VersionFormat fmt = formatCache.get(string); |
| if (fmt == null) { |
| fmt = this; |
| formatCache.put(string, fmt); |
| } |
| return fmt; |
| } |
| } |
| |
| /** |
| * Returns the string representation of this compiled format |
| */ |
| public synchronized String toString() { |
| if (fmtString == null) { |
| StringBuffer sb = new StringBuffer(); |
| toString(sb); |
| } |
| return fmtString; |
| } |
| |
| public synchronized void toString(StringBuffer sb) { |
| if (fmtString != null) |
| sb.append(fmtString); |
| else { |
| int start = sb.length(); |
| sb.append("format"); //$NON-NLS-1$ |
| if (topFragment.getPadValue() != null) { |
| sb.append('('); |
| topFragment.toString(sb); |
| sb.append(')'); |
| } else |
| topFragment.toString(sb); |
| fmtString = sb.substring(start); |
| } |
| } |
| } |
| |
| class RawFormat extends VersionFormat { |
| private static final long serialVersionUID = -6070590518921019745L; |
| |
| RawFormat(Fragment topFragment) { |
| super(topFragment); |
| } |
| |
| /** |
| * Parse but do not assign this format as the Version format nor the version |
| * string as the original. |
| */ |
| public Version parse(String version) { |
| List<Comparable<?>> vector = parse(version, 0, version.length()); |
| return OmniVersion.fromVector(vector, null, null); |
| } |
| |
| // Preserve singleton when deserialized |
| private Object readResolve() { |
| return RAW_FORMAT; |
| } |
| } |