| /******************************************************************************* |
| * Copyright (c) 2009, 2017 Cloudsmith Inc. and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Cloudsmith Inc. - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.equinox.internal.p2.metadata; |
| |
| import java.io.Serializable; |
| import java.util.*; |
| |
| /** |
| * Instances of this class represents the enum version format. |
| */ |
| class EnumDefinition implements Comparable<EnumDefinition>, Serializable { |
| static class EnumSegment implements Comparable<EnumSegment>, Serializable { |
| private static final long serialVersionUID = 4737907767214436543L; |
| |
| private final int ordinal; |
| private final EnumDefinition definition; |
| |
| EnumSegment(int ordinal, EnumDefinition definition) { |
| this.ordinal = ordinal; |
| this.definition = definition; |
| } |
| |
| @Override |
| public int compareTo(EnumSegment other) { |
| if (other == this) |
| return 0; |
| if (definition == other.definition) |
| // Same definition. Just compare ordinals |
| return ordinal - other.ordinal; |
| |
| String thisId = definition.getIdentifier(ordinal); |
| String otherId = other.definition.getIdentifier(other.ordinal); |
| if (thisId.equals(otherId)) |
| return 0; |
| |
| int thisOrdinalInOther = other.definition.getOrdinal(thisId); |
| int otherOrdinalInThis = definition.getOrdinal(otherId); |
| if (thisOrdinalInOther >= 0) { |
| if (otherOrdinalInThis >= 0) { |
| // Both identifiers exists in both enums. Let's see if both |
| // enums order them the same way |
| int thisOrder = ordinal - otherOrdinalInThis; |
| int otherOrder = thisOrdinalInOther - other.ordinal; |
| if (thisOrder > 0 && otherOrder > 0) |
| return 1; |
| if (thisOrder < 0 && otherOrder < 0) |
| return -1; |
| // Difference in opinion... |
| } else { |
| // Use the order in other since it has both identifiers and |
| // this enum does not |
| return thisOrdinalInOther - other.ordinal; |
| } |
| } else if (otherOrdinalInThis >= 0) { |
| // Use the order in this since it has both identifiers and the |
| // other does not. |
| return ordinal - otherOrdinalInThis; |
| } |
| |
| // We can't compare the enums since neither enum contain both identifiers |
| // or both do, but use different order. Fall back to comparing the definitions |
| return definition.compareTo(other.definition); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| return other == this || other instanceof EnumSegment && compareTo((EnumSegment) other) == 0; |
| } |
| |
| @Override |
| public int hashCode() { |
| return (1 + ordinal) * 31 + definition.getIdentifier(ordinal).hashCode(); |
| } |
| |
| int getOrdinal() { |
| return ordinal; |
| } |
| |
| String getIdentifier() { |
| return definition.getIdentifier(ordinal); |
| } |
| |
| void toString(StringBuffer sb) { |
| definition.toString(sb, ordinal); |
| } |
| |
| // For ligthweight deserialization |
| private Object readResolve() { |
| return definition.getSegment(ordinal); |
| } |
| } |
| |
| private static final long serialVersionUID = 7237775466362654473L; |
| private static final Map<EnumDefinition, EnumSegment[]> enumDefinitionCache = new HashMap<>(); |
| |
| private static EnumSegment[] getEnumSegments(EnumDefinition ed) { |
| EnumSegment[] values = enumDefinitionCache.get(ed); |
| if (values == null) { |
| int ordinal = ed.identifiers.length; |
| values = new EnumSegment[ordinal]; |
| while (--ordinal >= 0) |
| values[ordinal] = new EnumSegment(ordinal, ed); |
| enumDefinitionCache.put(ed, values); |
| } |
| return values; |
| } |
| |
| static EnumDefinition getEnumDefinition(List<List<String>> identifiers) { |
| nextEd: for (EnumDefinition ed : enumDefinitionCache.keySet()) { |
| String[][] defs = ed.identifiers; |
| int ordinal = defs.length; |
| if (ordinal != identifiers.size()) |
| continue; |
| |
| while (--ordinal >= 0) { |
| String[] def = defs[ordinal]; |
| List<String> ldef = identifiers.get(ordinal); |
| int idx = def.length; |
| if (ldef.size() != idx) |
| continue nextEd; |
| while (--idx >= 0) |
| if (!def[idx].equals(ldef.get(idx))) |
| continue nextEd; |
| } |
| return ed; |
| } |
| EnumDefinition ed = new EnumDefinition(identifiers); |
| getEnumSegments(ed); |
| return ed; |
| } |
| |
| private final String[][] identifiers; |
| private final int longestLength; |
| private final int shortestLength; |
| |
| private EnumDefinition(List<List<String>> identifiers) { |
| int ordinal = identifiers.size(); |
| String[][] defs = new String[ordinal][]; |
| int minLen = Integer.MAX_VALUE; |
| int maxLen = 0; |
| while (--ordinal >= 0) { |
| List<String> idents = identifiers.get(ordinal); |
| int idx = idents.size(); |
| String[] def = idents.toArray(new String[idx]); |
| defs[ordinal] = def; |
| while (--idx >= 0) { |
| int idLen = def[idx].length(); |
| if (idLen < minLen) |
| minLen = idLen; |
| if (idLen > maxLen) |
| maxLen = idLen; |
| } |
| } |
| this.shortestLength = minLen; |
| this.longestLength = maxLen; |
| this.identifiers = defs; |
| } |
| |
| static EnumSegment getSegment(List<List<String>> identifiers, int ordinal) { |
| return new EnumDefinition(identifiers).getSegment(ordinal); |
| } |
| |
| EnumSegment getSegment(int ordinal) { |
| return getEnumSegments(this)[ordinal]; |
| } |
| |
| /** |
| * Returns the ordinal for the given identifier |
| * @param identifier The identifier |
| * @return The ordinal of the identifier or -1 if it didn't exist. |
| */ |
| int getOrdinal(String identifier) { |
| if (identifier != null) { |
| int ordinal = identifiers.length; |
| while (--ordinal >= 0) { |
| String[] idents = identifiers[ordinal]; |
| int idx = idents.length; |
| while (--idx >= 0) |
| if (idents[idx].equals(identifier)) |
| return ordinal; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Returns the canonical identifier for the given ordinal. |
| * @param ordinal The ordinal number of the desired identifier |
| * @return The identifier or <code>null</code> if the ordinal is out of bounds |
| */ |
| String getIdentifier(int ordinal) { |
| return ordinal >= 0 && ordinal < identifiers.length ? identifiers[ordinal][0] : null; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = 1; |
| int ordinal = identifiers.length; |
| while (--ordinal > 0) { |
| String[] idents = identifiers[ordinal]; |
| int idx = idents.length; |
| while (--idx >= 0) |
| result = 31 * result + idents[idx].hashCode(); |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (o == this) |
| return true; |
| if (!(o instanceof EnumDefinition)) |
| return false; |
| String[][] otherIds = ((EnumDefinition) o).identifiers; |
| int ordinal = identifiers.length; |
| if (ordinal != otherIds.length) |
| return false; |
| while (--ordinal >= 0) |
| if (!Arrays.equals(identifiers[ordinal], otherIds[ordinal])) |
| return false; |
| return true; |
| } |
| |
| @Override |
| public int compareTo(EnumDefinition o) { |
| if (o == this) |
| return 0; |
| |
| int top = identifiers.length; |
| int cmp = top - o.identifiers.length; |
| if (cmp != 0) |
| return cmp; |
| |
| for (int idx = 0; idx < top; ++idx) { |
| cmp = identifiers[idx][0].compareTo(o.identifiers[idx][0]); |
| if (cmp != 0) |
| return cmp; |
| } |
| // this should never happen since we use a lightweight pattern |
| return 0; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer bld = new StringBuffer(); |
| toString(bld); |
| return bld.toString(); |
| } |
| |
| public void toString(StringBuffer bld) { |
| bld.append('{'); |
| int top = identifiers.length; |
| for (int ordinal = 0;;) { |
| String[] idents = identifiers[ordinal]; |
| int identsTop = idents.length; |
| bld.append(idents[0]); |
| for (int idx = 1; idx < identsTop; ++idx) { |
| bld.append('='); |
| bld.append(idents[idx]); |
| } |
| if (++ordinal == top) |
| break; |
| bld.append(','); |
| } |
| bld.append('}'); |
| } |
| |
| void toString(StringBuffer bld, int selectedOrdinal) { |
| bld.append('{'); |
| int top = identifiers.length; |
| for (int ordinal = 0;;) { |
| if (ordinal == selectedOrdinal) |
| bld.append('^'); |
| bld.append(identifiers[ordinal][0]); |
| if (++ordinal == top) |
| break; |
| bld.append(','); |
| } |
| bld.append('}'); |
| } |
| |
| /** |
| * Returns the length of the longest identifier. This method is |
| * used by the parser |
| * @return The length of the longest identifier |
| */ |
| int getLongestLength() { |
| return longestLength; |
| } |
| |
| /** |
| * Returns the length of the shortest identifier. This method is |
| * used by the parser |
| * @return The length of the shortest identifier |
| */ |
| int getShortestLength() { |
| return shortestLength; |
| } |
| } |