/******************************************************************************* * Copyright (c) 2011, 2014 Ericsson, Ecole Polytechnique de Montreal 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: Matthew Khouzam - Initial API and implementation * Contributors: Simon Marchi - Initial API and implementation *******************************************************************************/ package org.eclipse.tracecompass.ctf.core.event.types; import java.nio.ByteOrder; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.ctf.core.CTFException; import org.eclipse.tracecompass.ctf.core.event.io.BitBuffer; import org.eclipse.tracecompass.ctf.core.event.scope.IDefinitionScope; import com.google.common.collect.ImmutableMap; /** * A CTF enum declaration. * * The definition of a enum point basic data type. It will take the data from a * trace and store it (and make it fit) as an integer and a string. * * @version 1.0 * @author Matthew Khouzam * @author Simon Marchi */ public final class EnumDeclaration extends Declaration implements ISimpleDatatypeDeclaration { /** * A pair of longs class * * @since 1.1 */ public static class Pair { private final long fFirst; private final long fSecond; private Pair(long first, long second) { fFirst = first; fSecond = second; } /** * @return the first element */ public long getFirst() { return fFirst; } /** * @return the second element */ public long getSecond() { return fSecond; } } // ------------------------------------------------------------------------ // Attributes // ------------------------------------------------------------------------ private final EnumTable fTable = new EnumTable(); private final IntegerDeclaration fContainerType; private final Set<String> fLabels = new HashSet<>(); // ------------------------------------------------------------------------ // Constructors // ------------------------------------------------------------------------ /** * constructor * * @param containerType * the enum is an int, this is the type that the data is * contained in. If you have 1000 possible values, you need at * least a 10 bit enum. If you store 2 values in a 128 bit int, * you are wasting space. */ public EnumDeclaration(IntegerDeclaration containerType) { fContainerType = containerType; } // ------------------------------------------------------------------------ // Getters/Setters/Predicates // ------------------------------------------------------------------------ /** * * @return The container type */ public IntegerDeclaration getContainerType() { return fContainerType; } @Override public long getAlignment() { return this.getContainerType().getAlignment(); } @Override public int getMaximumSize() { return fContainerType.getMaximumSize(); } /** * @since 2.0 */ @Override public boolean isByteOrderSet() { return fContainerType.isByteOrderSet(); } /** * @since 2.0 */ @Override public ByteOrder getByteOrder() { return fContainerType.getByteOrder(); } // ------------------------------------------------------------------------ // Operations // ------------------------------------------------------------------------ @Override public EnumDefinition createDefinition(@Nullable IDefinitionScope definitionScope, String fieldName, BitBuffer input) throws CTFException { alignRead(input); IntegerDefinition value = getContainerType().createDefinition(definitionScope, fieldName, input); return new EnumDefinition(this, definitionScope, fieldName, value); } /** * Add a value. Do not overlap, this is <em><strong>not</strong></em> an * interval tree. * * @param low * lowest value that this int can be to have label as a return * string * @param high * highest value that this int can be to have label as a return * string * @param label * the name of the value. * @return was the value be added? true == success */ public boolean add(long low, long high, @Nullable String label) { fLabels.add(label); return fTable.add(low, high, label); } /** * Add a value. Do not overlap, this is <em><strong>not</strong></em> an * interval tree. This could be seen more as a collection of segments. * * @param label * the name of the value. * @return was the value be added? true == success * @since 2.0 */ public boolean add(@Nullable String label) { fLabels.add(label); return fTable.add(label); } /** * Check if the label for a value (enum a{day=0,night=1} would return "day" * for query(0) * * @param value * the value to lookup * @return the label of that value, can be null */ public @Nullable String query(long value) { return fTable.query(value); } /** * Get the lookup table * * @return the lookup table * @since 1.1 */ public Map<String, Pair> getEnumTable() { ImmutableMap.Builder<String, Pair> builder = new ImmutableMap.Builder<>(); for (LabelAndRange range : fTable.ranges) { builder.put(range.getLabel(), new Pair(range.low, range.high)); } return builder.build(); } /** * Gets a set of labels of the enum * * @return A set of labels of the enum, can be empty but not null */ public Set<String> getLabels() { return Collections.unmodifiableSet(fLabels); } /* * Maps integer range -> string. A simple list for now, but feel free to * optimize it. Babeltrace suggests an interval tree. */ private class EnumTable { private final List<LabelAndRange> ranges = new LinkedList<>(); public EnumTable() { } public synchronized boolean add(@Nullable String label) { LabelAndRange lastAdded = ranges.isEmpty() ? new LabelAndRange(-1, -1, "") : ranges.get(ranges.size() - 1); //$NON-NLS-1$ return add(lastAdded.low + 1, lastAdded.high + 1, label); } public synchronized boolean add(long low, long high, @Nullable String label) { LabelAndRange newRange = new LabelAndRange(low, high, label); for (LabelAndRange r : ranges) { if (r.intersects(newRange)) { return false; } } ranges.add(newRange); return true; } /** * Return the first label that matches a value * * @param value * the value to query * @return the label corresponding to that value */ public synchronized @Nullable String query(long value) { for (LabelAndRange r : ranges) { if (r.intersects(value)) { return r.getLabel(); } } return null; } @Override public synchronized int hashCode() { final int prime = 31; int result = 1; for (LabelAndRange range : ranges) { result = prime * result + range.hashCode(); } return result; } @Override public synchronized boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } EnumTable other = (EnumTable) obj; if (ranges.size() != other.ranges.size()) { return false; } for (int i = 0; i < ranges.size(); i++) { if (!ranges.get(i).equals(other.ranges.get(i))) { return false; } } return true; } } private static class LabelAndRange { private final long low, high; private final @Nullable String fLabel; /** * Get the label * * @return the label */ public @Nullable String getLabel() { return fLabel; } public LabelAndRange(long low, long high, @Nullable String str) { this.low = low; this.high = high; this.fLabel = str; } public boolean intersects(long i) { return (i >= this.low) && (i <= this.high); } public boolean intersects(LabelAndRange other) { return this.intersects(other.low) || this.intersects(other.high); } @Override public int hashCode() { final int prime = 31; int result = 1; final String label = fLabel; result = prime * result + ((label == null) ? 0 : label.hashCode()); result = prime * result + (int) (high ^ (high >>> 32)); result = prime * result + (int) (low ^ (low >>> 32)); return result; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } LabelAndRange other = (LabelAndRange) obj; final String label = fLabel; if (label == null) { if (other.fLabel != null) { return false; } } else if (!label.equals(other.fLabel)) { return false; } if (high != other.high) { return false; } if (low != other.low) { return false; } return true; } } @Override public String toString() { /* Only used for debugging */ StringBuilder sb = new StringBuilder(); sb.append("[declaration] enum["); //$NON-NLS-1$ for (String label : fLabels) { sb.append("label:").append(label).append(' '); //$NON-NLS-1$ } sb.append("type:").append(fContainerType.toString()); //$NON-NLS-1$ sb.append(']'); return sb.toString(); } @Override public int hashCode() { final int prime = 31; int result = prime + fContainerType.hashCode(); for (String label : fLabels) { result = prime * result + label.hashCode(); } result = prime * result + fTable.hashCode(); return result; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } EnumDeclaration other = (EnumDeclaration) obj; if (!fContainerType.equals(other.fContainerType)) { return false; } if (fLabels.size() != other.fLabels.size()) { return false; } if (!fLabels.containsAll(other.fLabels)) { return false; } if (!fTable.equals(other.fTable)) { return false; } return true; } @Override public boolean isBinaryEquivalent(@Nullable IDeclaration obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } EnumDeclaration other = (EnumDeclaration) obj; if (!fContainerType.isBinaryEquivalent(other.fContainerType)) { return false; } if (fLabels.size() != other.fLabels.size()) { return false; } if (!fLabels.containsAll(other.fLabels)) { return false; } if (!fTable.equals(other.fTable)) { return false; } return true; } }