Commit | Line | Data |
---|---|---|
d69a6555 GB |
1 | /******************************************************************************* |
2 | * Copyright (c) 2016 École Polytechnique de Montréal | |
3 | * | |
4 | * All rights reserved. This program and the accompanying materials are | |
5 | * made available under the terms of the Eclipse Public License v1.0 which | |
6 | * accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | *******************************************************************************/ | |
9 | ||
10 | package org.eclipse.tracecompass.internal.provisional.statesystem.core.statevalue; | |
11 | ||
12 | import java.nio.BufferOverflowException; | |
13 | ||
14 | import org.eclipse.jdt.annotation.Nullable; | |
15 | import org.eclipse.tracecompass.internal.statesystem.core.Activator; | |
16 | import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue; | |
17 | ||
18 | import com.google.common.annotations.VisibleForTesting; | |
19 | ||
20 | /** | |
21 | * This allows to define custom state values. | |
22 | * | |
23 | * Each sub-class should define a {@link CustomStateValueFactory} that has to be | |
24 | * registered, for example by the analysis using this state value type, through | |
25 | * the | |
26 | * {@link CustomStateValue#registerCustomFactory(byte, CustomStateValueFactory)} | |
27 | * method. | |
28 | * | |
29 | * Note to implementers: These state values are meant to be used in data | |
30 | * structures that save information on objects that varies in time, like a state | |
31 | * system. It will often be made persistent on disk at some point, so it is | |
32 | * suggested to make the child classes immutable. | |
33 | * | |
34 | * Data structures using these custom values will often keep the values in a | |
35 | * transient state before they are sent to be persisted. For persistence, it | |
36 | * will request the size of the value, then write its bytes to a buffer. Once | |
37 | * the size is requested, the value is about to be saved, so it is important | |
38 | * that its value and size do not change after that, as it may get in an | |
39 | * incoherent state, or, worse throw runtime exceptions. | |
40 | * | |
41 | * @author Geneviève Bastien | |
42 | * @since 2.0 | |
43 | */ | |
44 | public abstract class CustomStateValue extends TmfStateValue { | |
45 | ||
46 | /* Minimum size of the state value */ | |
47 | private static final int MIN_SIZE = Byte.BYTES; | |
48 | ||
49 | /** | |
50 | * Custom value factory interface. Each custom state value should have a | |
51 | * corresponding factory to re-create the object from a ByteBuffer | |
52 | */ | |
53 | @FunctionalInterface | |
54 | public interface CustomStateValueFactory { | |
55 | ||
56 | /** | |
57 | * Get the custom state value from a byte buffer | |
58 | * | |
59 | * @param buffer | |
60 | * the byte buffer | |
61 | * @return the custom state value | |
62 | */ | |
63 | CustomStateValue readCustomValue(ISafeByteBufferReader buffer); | |
64 | } | |
65 | ||
66 | private static final CustomStateValueFactory[] CUSTOM_FACTORIES = new CustomStateValueFactory[256]; | |
67 | ||
68 | /** | |
69 | * Register the custom factory that will be reused to create instances of | |
70 | * custom state value objects | |
71 | * | |
72 | * @param customId | |
73 | * The ID of this custom type. For possible values of this ID, | |
74 | * see {@link #getCustomTypeId()} | |
75 | * @param factory | |
76 | * The factory used to create a new custom type object of this ID | |
77 | */ | |
78 | public static void registerCustomFactory(byte customId, CustomStateValueFactory factory) { | |
79 | if (customId >= 0 && customId <= 20) { | |
80 | throw new IllegalArgumentException("State value IDs between 0 and 20 are reserved for built-in state value types"); //$NON-NLS-1$ | |
81 | } | |
82 | CustomStateValueFactory currentFactory = CUSTOM_FACTORIES[customId - Byte.MIN_VALUE]; | |
83 | if (currentFactory == null) { | |
84 | CUSTOM_FACTORIES[customId - Byte.MIN_VALUE] = factory; | |
85 | } else if (currentFactory != factory) { | |
86 | throw new IllegalStateException("Already a custom factory registered for " + Byte.toString(customId)); //$NON-NLS-1$ | |
87 | } | |
88 | } | |
89 | ||
90 | /** | |
91 | * Unregisters the custom factory | |
92 | * | |
93 | * @param customId | |
94 | * The ID of this custom type | |
95 | */ | |
96 | @VisibleForTesting | |
97 | protected static void unregisterCustomFactory(byte customId) { | |
98 | CUSTOM_FACTORIES[customId - Byte.MIN_VALUE] = null; | |
99 | } | |
100 | ||
101 | /** | |
102 | * Get the custom factory for a byte | |
103 | * | |
104 | * @param customId | |
105 | * the custom factory key | |
106 | * @return the custom factory or null if none is registered for the key | |
107 | */ | |
108 | public static @Nullable CustomStateValueFactory getCustomFactory(byte customId) { | |
109 | return CUSTOM_FACTORIES[customId - Byte.MIN_VALUE]; | |
110 | } | |
111 | ||
112 | /** | |
113 | * Read a serialized value (obtained with the | |
114 | * {@link #serialize(ISafeByteBufferWriter)} method) into a real | |
115 | * {@link CustomStateValue} object. | |
116 | * | |
117 | * @param buffer | |
118 | * The byte buffer to read from | |
119 | * @return The state value object | |
120 | */ | |
121 | public static TmfStateValue readSerializedValue(ISafeByteBufferReader buffer) { | |
122 | /* the first byte = the custom type id */ | |
123 | byte customType = buffer.get(); | |
124 | CustomStateValueFactory customFactory = CustomStateValue.getCustomFactory(customType); | |
125 | if (customFactory == null) { | |
126 | Activator.getDefault().logWarning("Custom factory for type " + customType + " does not exist"); //$NON-NLS-1$ //$NON-NLS-2$ | |
127 | return TmfStateValue.nullValue(); | |
128 | } | |
129 | ||
130 | return customFactory.readCustomValue(buffer); | |
131 | } | |
132 | ||
133 | /** | |
134 | * Get custom type id. The value should be between {@link Byte#MIN_VALUE} | |
135 | * and {@link Byte#MAX_VALUE}, but not between {@code 0} and {@code 20} that | |
136 | * are reserved for built-in state value types. | |
137 | * | |
138 | * @return the custom type ID | |
139 | */ | |
140 | protected abstract Byte getCustomTypeId(); | |
141 | ||
142 | /** | |
143 | * Serialize this state value into the byte buffer. This method should only | |
144 | * write the payload of the state value and should match what will be read | |
145 | * by the factory when deserializing. | |
146 | * | |
147 | * This serialized value must contain all the payload of the state value | |
148 | * | |
149 | * @param buffer | |
150 | * The ByteBuffer to write the values to | |
151 | */ | |
152 | protected abstract void serializeValue(ISafeByteBufferWriter buffer); | |
153 | ||
154 | /** | |
155 | * Get the serialized size of this state value. The size must not exceed | |
156 | * {@link Short#MAX_VALUE - MIN_SIZE}, otherwise serialization might throw a | |
157 | * {@link BufferOverflowException} | |
158 | * | |
159 | * @return The serialized size of this state value | |
160 | */ | |
161 | protected abstract int getSerializedValueSize(); | |
162 | ||
163 | /** | |
164 | * Serialize this custom state value into a byte buffer. It calls the | |
165 | * serialization of the state value itself and adds the specific fields to | |
166 | * interpret that byte array. | |
167 | * | |
168 | * The format of the value is [custom type (byte)][payload] | |
169 | * | |
170 | * The total serialized size should never exceed {@link Short#MAX_VALUE} | |
171 | * | |
172 | * @param buffer | |
173 | * The ByteBuffer to write the values to | |
174 | * | |
175 | * @throws BufferOverflowException | |
176 | * If the serialized size of the value ends up larger than the | |
177 | * maximum of {@link Short#MAX_VALUE} and the implementation has | |
178 | * no way of handling it | |
179 | */ | |
180 | public final void serialize(ISafeByteBufferWriter buffer) { | |
181 | buffer.put(getCustomTypeId()); | |
182 | serializeValue(buffer); | |
183 | } | |
184 | ||
185 | /** | |
186 | * Get the serialized size of this state value. This size will be used to | |
187 | * allow the buffer that will be sent to the | |
188 | * {@link #serialize(ISafeByteBufferWriter)} method | |
189 | * | |
190 | * @return The size of the serialized value | |
191 | */ | |
192 | public final int getSerializedSize() { | |
193 | int size = getSerializedValueSize(); | |
194 | if (size > Short.MAX_VALUE - MIN_SIZE) { | |
195 | throw new ArrayIndexOutOfBoundsException("Serialized state value is larger than the maximum allowed size of " + (Short.MAX_VALUE - MIN_SIZE) + ": " + size); //$NON-NLS-1$ //$NON-NLS-2$ | |
196 | } | |
197 | return size + MIN_SIZE; | |
198 | } | |
199 | ||
200 | @Override | |
201 | public final Type getType() { | |
202 | return Type.CUSTOM; | |
203 | } | |
204 | ||
205 | @Override | |
206 | public boolean isNull() { | |
207 | return false; | |
208 | } | |
209 | ||
210 | } |