Skip to content

Commit 8aec82f

Browse files
authored
GUACAMOLE-2036: Merge changes to reuse buffers received by parser when converting instructions back to character arrays.
2 parents b08e0be + a672229 commit 8aec82f

File tree

3 files changed

+190
-35
lines changed

3 files changed

+190
-35
lines changed

guacamole-common/src/main/java/org/apache/guacamole/io/ReaderGuacamoleReader.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.io.Reader;
2525
import java.net.SocketException;
2626
import java.net.SocketTimeoutException;
27-
import java.util.Arrays;
2827
import org.apache.guacamole.GuacamoleConnectionClosedException;
2928
import org.apache.guacamole.GuacamoleException;
3029
import org.apache.guacamole.GuacamoleServerException;
@@ -92,7 +91,7 @@ public char[] read() throws GuacamoleException {
9291
if (instruction == null)
9392
return null;
9493

95-
return instruction.toString().toCharArray();
94+
return instruction.toCharArray();
9695
}
9796

9897
@Override

guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleInstruction.java

Lines changed: 112 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -41,41 +41,91 @@ public class GuacamoleInstruction {
4141
private final List<String> args;
4242

4343
/**
44-
* The cached result of converting this GuacamoleInstruction to the format
45-
* used by the Guacamole protocol.
44+
* The cached String result of converting this GuacamoleInstruction to the
45+
* format used by the Guacamole protocol.
46+
*
47+
* @see #toString()
4648
*/
47-
private String protocolForm = null;
49+
private String rawString = null;
4850

4951
/**
50-
* Creates a new GuacamoleInstruction having the given Operation and
51-
* list of arguments values.
52+
* The cached char[] result of converting this GuacamoleInstruction to the
53+
* format used by the Guacamole protocol.
5254
*
53-
* @param opcode The opcode of the instruction to create.
54-
* @param args The list of argument values to provide in the new
55-
* instruction if any.
55+
* @see #toCharArray()
56+
*/
57+
private char[] rawChars = null;
58+
59+
/**
60+
* Creates a new GuacamoleInstruction having the given opcode and list of
61+
* argument values.
62+
*
63+
* @param opcode
64+
* The opcode of the instruction to create.
65+
*
66+
* @param args
67+
* The list of argument values to provide in the new instruction, if
68+
* any.
5669
*/
5770
public GuacamoleInstruction(String opcode, String... args) {
5871
this.opcode = opcode;
5972
this.args = Collections.unmodifiableList(Arrays.asList(args));
6073
}
6174

6275
/**
63-
* Creates a new GuacamoleInstruction having the given Operation and
64-
* list of arguments values. The list given will be used to back the
65-
* internal list of arguments and the list returned by getArgs().
76+
* Creates a new GuacamoleInstruction having the given opcode and list of
77+
* argument values. The list given will be used to back the internal list of
78+
* arguments and the list returned by {@link #getArgs()}.
79+
* <p>
80+
* The provided argument list may not be modified in any way after being
81+
* provided to this constructor. Doing otherwise will result in undefined
82+
* behavior.
83+
*
84+
* @param opcode
85+
* The opcode of the instruction to create.
6686
*
67-
* @param opcode The opcode of the instruction to create.
68-
* @param args The list of argument values to provide in the new
69-
* instruction if any.
87+
* @param args
88+
* The list of argument values to provide in the new instruction, if
89+
* any.
7090
*/
7191
public GuacamoleInstruction(String opcode, List<String> args) {
7292
this.opcode = opcode;
7393
this.args = Collections.unmodifiableList(args);
7494
}
7595

96+
/**
97+
* Creates a new GuacamoleInstruction having the given opcode, list of
98+
* argument values, and underlying protocol representation. The list given
99+
* will be used to back the internal list of arguments and the list
100+
* returned by {@link #getArgs()}. The provided protocol representation
101+
* will be used to back the internal protocol representation and values
102+
* returned by {@link #toCharArray()} and {@link #toString()}.
103+
* <p>
104+
* Neither the provided argument list nor the provided protocol
105+
* representation may be modified in any way after being provided to this
106+
* constructor. Doing otherwise will result in undefined behavior.
107+
*
108+
* @param opcode
109+
* The opcode of the instruction to create.
110+
*
111+
* @param args
112+
* The list of argument values to provide in the new instruction, if
113+
* any.
114+
*
115+
* @param raw
116+
* The underlying representation of this instruction as would be sent
117+
* over the network via the Guacamole protocol.
118+
*/
119+
public GuacamoleInstruction(String opcode, List<String> args, char[] raw) {
120+
this(opcode, args);
121+
this.rawChars = raw;
122+
}
123+
76124
/**
77125
* Returns the opcode associated with this GuacamoleInstruction.
78-
* @return The opcode associated with this GuacamoleInstruction.
126+
*
127+
* @return
128+
* The opcode associated with this GuacamoleInstruction.
79129
*/
80130
public String getOpcode() {
81131
return opcode;
@@ -86,8 +136,9 @@ public String getOpcode() {
86136
* GuacamoleInstruction. Note that the List returned is immutable.
87137
* Attempts to modify the list will result in exceptions.
88138
*
89-
* @return A List of all argument values specified for this
90-
* GuacamoleInstruction.
139+
* @return
140+
* An unmodifiable List of all argument values specified for this
141+
* GuacamoleInstruction.
91142
*/
92143
public List<String> getArgs() {
93144
return args;
@@ -122,28 +173,58 @@ public String toString() {
122173

123174
// Avoid rebuilding Guacamole protocol form of instruction if already
124175
// known
125-
if (protocolForm == null) {
176+
if (rawString == null) {
126177

127-
StringBuilder buff = new StringBuilder();
178+
// Prefer to construct String from existing char array, rather than
179+
// reconstruct protocol from scratch
180+
if (rawChars != null)
181+
rawString = new String(rawChars);
128182

129-
// Write opcode
130-
appendElement(buff, opcode);
183+
// Reconstruct protocol details only if truly necessary
184+
else {
131185

132-
// Write argument values
133-
for (String value : args) {
134-
buff.append(',');
135-
appendElement(buff, value);
136-
}
186+
StringBuilder buff = new StringBuilder();
137187

138-
// Write terminator
139-
buff.append(';');
188+
// Write opcode
189+
appendElement(buff, opcode);
140190

141-
// Cache result for future calls
142-
protocolForm = buff.toString();
191+
// Write argument values
192+
for (String value : args) {
193+
buff.append(',');
194+
appendElement(buff, value);
195+
}
196+
197+
// Write terminator
198+
buff.append(';');
199+
200+
// Cache result for future calls
201+
rawString = buff.toString();
202+
203+
}
143204

144205
}
145206

146-
return protocolForm;
207+
return rawString;
208+
209+
}
210+
211+
/**
212+
* Returns this GuacamoleInstruction in the form it would be sent over the
213+
* Guacamole protocol. The returned char[] MUST NOT be modified. If the
214+
* returned char[] is modified, the results of doing so are undefined.
215+
*
216+
* @return
217+
* This GuacamoleInstruction in the form it would be sent over the
218+
* Guacamole protocol. The returned char[] MUST NOT be modified.
219+
*/
220+
public char[] toCharArray() {
221+
222+
// Avoid rebuilding Guacamole protocol form of instruction if already
223+
// known
224+
if (rawChars == null)
225+
rawChars = toString().toCharArray();
226+
227+
return rawChars;
147228

148229
}
149230

guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleParser.java

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,18 @@ private enum State {
108108
*/
109109
private final String elements[] = new String[INSTRUCTION_MAX_ELEMENTS];
110110

111+
/**
112+
* A copy of the raw protocol data that has been parsed for the current
113+
* instruction. This value is maintained by {@link #append(char[], int, int)}.
114+
*/
115+
private final char rawInstruction[] = new char[INSTRUCTION_MAX_LENGTH];
116+
117+
/**
118+
* The offset within {@link #rawInstruction} that new data should be
119+
* appended. This value is maintained by {@link #append(char[], int, int)}.
120+
*/
121+
private int rawInstructionOffset = 0;
122+
111123
/**
112124
* Appends data from the given buffer to the current instruction.
113125
*
@@ -130,6 +142,71 @@ private enum State {
130142
*/
131143
public int append(char chunk[], int offset, int length) throws GuacamoleException {
132144

145+
int originalOffset = offset;
146+
int originalLength = length;
147+
148+
// Process as much of the received chunk as possible
149+
while (length > 0) {
150+
151+
int appended = processElement(chunk, offset, length);
152+
if (appended == 0)
153+
break;
154+
155+
length -= appended;
156+
offset += appended;
157+
}
158+
159+
// Update the raw copy of the received instruction with whatever data
160+
// has now been processed
161+
int charsParsed = originalLength - length;
162+
if (charsParsed > 0) {
163+
164+
System.arraycopy(chunk, originalOffset, rawInstruction, rawInstructionOffset, charsParsed);
165+
rawInstructionOffset += charsParsed;
166+
167+
// If the instruction is now complete, we're good to store the
168+
// parsed instruction for future retrieval via next()
169+
if (state == State.COMPLETE) {
170+
parsedInstruction = new GuacamoleInstruction(elements[0], Arrays.asList(elements).subList(1, elementCount),
171+
Arrays.copyOf(rawInstruction, rawInstructionOffset));
172+
rawInstructionOffset = 0;
173+
}
174+
175+
}
176+
177+
return charsParsed;
178+
179+
}
180+
181+
/**
182+
* Processes additional data from the given buffer, potentially adding
183+
* another element to the current instruction being parsed. This function
184+
* will need to be invoked multiple times per instruction until all data
185+
* for that instruction is ready.
186+
* <p>
187+
* This function DOES NOT update {@link #parsedInstruction}. The caller
188+
* ({@link #append(char[], int, int)}) must update this as necessary when
189+
* the parser {@link #state} indicates the instruction is complete.
190+
*
191+
* @param chunk
192+
* The buffer containing the data to append.
193+
*
194+
* @param offset
195+
* The offset within the buffer where the data begins.
196+
*
197+
* @param length
198+
* The length of the data to append.
199+
*
200+
* @return
201+
* The number of characters appended, or 0 if complete instructions
202+
* have already been parsed and must be read via next() before more
203+
* data can be appended.
204+
*
205+
* @throws GuacamoleException
206+
* If an error occurs while parsing the new data.
207+
*/
208+
private int processElement(char chunk[], int offset, int length) throws GuacamoleException {
209+
133210
int charsParsed = 0;
134211

135212
// Do not exceed maximum number of elements
@@ -215,8 +292,6 @@ else if (Character.isSurrogatePair(chunk[offset + charsParsed + elementLength -
215292
// If semicolon, store end-of-instruction
216293
case ';':
217294
state = State.COMPLETE;
218-
parsedInstruction = new GuacamoleInstruction(elements[0],
219-
Arrays.asList(elements).subList(1, elementCount));
220295
break;
221296

222297
// If comma, move on to next element

0 commit comments

Comments
 (0)