blob: fffcedad78f4564ed4eabf9c230bb636353daaf0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Ecole Polytechnique de Montreal, Ericsson
*
* All rights reserved. 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
******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.analysis.xml.core.segment;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.datastore.core.interval.IHTIntervalReader;
import org.eclipse.tracecompass.datastore.core.serialization.ISafeByteBufferWriter;
import org.eclipse.tracecompass.datastore.core.serialization.SafeByteBufferFactory;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.Activator;
import org.eclipse.tracecompass.segmentstore.core.IContentSegment;
import org.eclipse.tracecompass.segmentstore.core.ISegment;
import org.eclipse.tracecompass.segmentstore.core.segment.interfaces.INamedSegment;
import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue;
/**
* This class implements an XML Pattern Segment. This type of segment has
* content and a default timestamp, which is the start time of the segment.
*
* @author Jean-Christian Kouame
*/
public class TmfXmlPatternSegment implements INamedSegment, IContentSegment {
/**
* The serial version UID
*/
private static final long serialVersionUID = 3556323761465412078L;
/* 'Byte' equivalent for state values types */
private static final byte TYPE_NULL = -1;
private static final byte TYPE_INTEGER = 0;
private static final byte TYPE_STRING = 1;
private static final byte TYPE_LONG = 2;
private final long fStart;
private final long fEnd;
private final @NonNull String fSegmentName;
private transient @NonNull Map<@NonNull String, @NonNull Object> fContent;
/**
* The reader for this segment class
*/
public static final @NonNull IHTIntervalReader<@NonNull ISegment> READER = buffer -> {
long start = buffer.getLong();
long end = buffer.getLong();
// Reading the now unused scale
buffer.getInt();
String segmentName = buffer.getString();
int contentSize = buffer.getInt();
final Map<@NonNull String, @NonNull Object> content = new HashMap<>();
for (int i = 0; i < contentSize; i++) {
String name = buffer.getString().intern();
Byte type = buffer.get();
Object value;
switch (type) {
case TYPE_INTEGER:
value = buffer.getInt();
break;
case TYPE_LONG:
value = buffer.getLong();
break;
case TYPE_STRING:
value = buffer.getString().intern();
break;
default:
value = null;
Activator.logError("Read segment failed : Invalid data, value will be ignored"); //$NON-NLS-1$
}
if (value != null) {
content.put(name, value);
}
}
return new TmfXmlPatternSegment(start, end, segmentName, content);
};
/**
* Constructs an XML pattern segment
*
* @param start
* Start time of the pattern segment
* @param end
* End time of the pattern segment
* @param segmentName
* Name of the pattern segment
* @param fields
* Fields of the pattern segment
*/
public TmfXmlPatternSegment(long start, long end, String segmentName, @NonNull Map<@NonNull String, @NonNull Object> fields) {
fStart = start;
fEnd = end;
fSegmentName = String.valueOf(segmentName);
fContent = Collections.unmodifiableMap(fields);
}
/**
* Get the content of the pattern segment
*
* @return The content
*/
@Override
public Map<@NonNull String, @NonNull Object> getContent() {
return fContent;
}
@Override
public String getName() {
return fSegmentName;
}
@Override
public int compareTo(@NonNull ISegment o) {
int ret = INamedSegment.super.compareTo(o);
if (ret != 0) {
return ret;
}
ret = IContentSegment.super.compareTo(o);
if (ret != 0) {
return ret;
}
return toString().compareTo(o.toString());
}
@Override
public long getStart() {
return fStart;
}
@Override
public long getEnd() {
return fEnd;
}
@Override
public String toString() {
return new StringBuilder(getClass().getSimpleName())
.append(", [fTimestampStart=").append(getStart()) //$NON-NLS-1$
.append(", fTimestampEnd=").append(getEnd()) //$NON-NLS-1$
.append(", duration= ").append(getLength()) //$NON-NLS-1$
.append(", fName=").append(getName()) //$NON-NLS-1$
.append(", fContent=").append(getContent()) //$NON-NLS-1$
.append("]").toString(); //$NON-NLS-1$
}
@Override
public void writeSegment(@NonNull ISafeByteBufferWriter buffer) {
buffer.putLong(fStart);
buffer.putLong(fEnd);
// Legacy support, was scale
// FIXME: Ideally, we should version the writer and remove this value
buffer.putInt(0);
buffer.putString(fSegmentName);
// Write the number of fields
buffer.putInt(fContent.size());
// Write the fields
for (Map.Entry<String, Object> entry : fContent.entrySet()) {
buffer.putString(entry.getKey());
final Object value = entry.getValue();
final byte type = getByteFromObject(value);
buffer.put(type);
switch (type) {
case TYPE_NULL:
break;
case TYPE_INTEGER:
buffer.putInt((Integer) value);
break;
case TYPE_LONG:
buffer.putLong((Long) value);
break;
case TYPE_STRING:
final String string = (String) value;
buffer.putString(Objects.requireNonNull(string));
break;
default:
Activator.logError("Write object failed : Invalid data"); //$NON-NLS-1$
}
}
}
@Override
public int getSizeOnDisk() {
int size = 2 * Long.BYTES + Integer.BYTES + SafeByteBufferFactory.getStringSizeInBuffer(fSegmentName) + Integer.BYTES;
for (Map.Entry<String, Object> entry : fContent.entrySet()) {
size += SafeByteBufferFactory.getStringSizeInBuffer(entry.getKey());
final Object value = entry.getValue();
final byte type = getByteFromObject(value);
size += Byte.BYTES;
switch (type) {
case TYPE_NULL:
break;
case TYPE_INTEGER:
size += Integer.BYTES;
break;
case TYPE_LONG:
size += Long.BYTES;
break;
case TYPE_STRING:
final String string = (String) value;
size += SafeByteBufferFactory.getStringSizeInBuffer(Objects.requireNonNull(string));
break;
default:
Activator.logError("get segment size on disk failed : Invalid data"); //$NON-NLS-1$
}
}
return size;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
// Write the number of fields
out.writeInt(fContent.size());
// Write the fields
for (Map.Entry<String, Object> entry : fContent.entrySet()) {
out.writeInt(entry.getKey().length());
out.writeBytes(entry.getKey());
final Object value = entry.getValue();
final byte type = getByteFromObject(value);
out.writeByte(type);
switch (type) {
case TYPE_NULL:
break;
case TYPE_INTEGER:
out.writeInt((Integer) value);
break;
case TYPE_LONG:
out.writeLong((Long) value);
break;
case TYPE_STRING:
final String string = (String) value;
out.writeInt(string.length());
out.writeBytes(string);
break;
default:
throw new IOException("Write object failed : Invalid data"); //$NON-NLS-1$
}
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
int contentSize = in.readInt();
final Map<@NonNull String, @NonNull Object> content = new HashMap<>();
for (int i = 0; i < contentSize; i++) {
int length = in.readInt();
byte[] bytes = new byte[length];
in.read(bytes, 0, length);
String name = new String(bytes).intern();
Byte type = in.readByte();
ITmfStateValue value;
switch (type) {
case TYPE_NULL:
value = TmfStateValue.nullValue();
break;
case TYPE_INTEGER:
value = TmfStateValue.newValueInt(in.readInt());
break;
case TYPE_LONG:
value = TmfStateValue.newValueLong(in.readLong());
break;
case TYPE_STRING:
length = in.readInt();
bytes = new byte[length];
in.read(bytes, 0, length);
value = TmfStateValue.newValueString(new String(bytes).intern());
break;
default:
throw new IOException("Read object failed : Invalid data"); //$NON-NLS-1$
}
content.put(name, value);
}
fContent = content;
}
/**
* Here we determine how state values "types" are written in the 8-bit field
* that indicates the value type in the file.
*/
private static byte getByteFromObject(Object value) {
if (value == null) {
return TYPE_NULL;
}
if (value instanceof Integer) {
return TYPE_INTEGER;
}
if (value instanceof Long) {
return TYPE_LONG;
}
if (value instanceof String) {
return TYPE_STRING;
}
/* Should not happen, only the previous types are supported */
throw new IllegalStateException("Data type " + value.getClass() + " not supported"); //$NON-NLS-1$ //$NON-NLS-2$
}
}