blob: 865e78869f58dca1da24bca7e19aa52fde3fa671 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}