4
* Copyright (c) 2012, CloudBees, Inc.
6
* Permission is hereby granted, free of charge, to any person obtaining a copy
7
* of this software and associated documentation files (the "Software"), to deal
8
* in the Software without restriction, including without limitation the rights
9
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
* copies of the Software, and to permit persons to whom the Software is
11
* furnished to do so, subject to the following conditions:
13
* The above copyright notice and this permission notice shall be included in
14
* all copies or substantial portions of the Software.
16
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
import java.io.Serializable;
29
import org.apache.commons.lang.StringUtils;
31
import com.thoughtworks.xstream.XStream;
32
import com.thoughtworks.xstream.converters.Converter;
33
import com.thoughtworks.xstream.converters.MarshallingContext;
34
import com.thoughtworks.xstream.converters.UnmarshallingContext;
35
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
36
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
39
* {@link TreeString} is an alternative string representation that saves the
40
* memory when you have a large number of strings that share common prefixes
41
* (such as various file names.)
43
* {@link TreeString} can be built with {@link TreeStringBuilder}.
45
* @author Kohsuke Kawaguchi
49
@SuppressWarnings("PMD")
50
public final class TreeString implements Serializable {
51
private static final long serialVersionUID = 3621959682117480904L;
54
* Parent node that represents the prefix.
56
private TreeString parent;
59
* {@link #parent}+{@link #label} is the string value of this node.
64
* Creates a new root {@link TreeString}
66
/* package */TreeString() {
70
/* package */TreeString(final TreeString parent, final String label) {
71
assert parent == null || label.length() > 0; // if there's a parent,
72
// label can't be empty.
75
this.label = label.toCharArray(); // string created as a substring of
76
// another string can have a lot of
77
// garbage attached to it.
80
/* package */String getLabel() {
81
return new String(label);
85
* Inserts a new node between this node and its parent, and returns the
86
* newly inserted node.
88
* This operation doesn't change the string representation of this node.
90
/* package */TreeString split(final String prefix) {
91
assert getLabel().startsWith(prefix);
92
char[] suffix = new char[label.length - prefix.length()];
93
System.arraycopy(label, prefix.length(), suffix, 0, suffix.length);
95
TreeString middle = new TreeString(parent, prefix);
103
* How many nodes do we have from the root to this node (including 'this'
104
* itself?) Thus depth of the root node is 1.
106
private int depth() {
108
for (TreeString p = this; p != null; p = p.parent) {
115
public boolean equals(final Object rhs) {
119
return rhs.getClass() == TreeString.class
120
&& ((TreeString)rhs).getLabel().equals(getLabel());
124
public int hashCode() {
125
int h = parent == null ? 0 : parent.hashCode();
127
for (int i = 0; i < label.length; i++) {
128
h = 31 * h + label[i];
131
assert toString().hashCode() == h;
136
* Returns the full string representation.
139
public String toString() {
140
char[][] tokens = new char[depth()][];
141
int i = tokens.length;
143
for (TreeString p = this; p != null; p = p.parent) {
144
tokens[--i] = p.label;
145
sz += p.label.length;
148
StringBuilder buf = new StringBuilder(sz);
149
for (char[] token : tokens) {
153
return buf.toString();
157
* Interns {@link #label}
159
/* package */void dedup(final Map<String, char[]> table) {
160
String l = getLabel();
161
char[] v = table.get(l);
170
public boolean isBlank() {
171
return StringUtils.isBlank(toString());
174
public static String toString(final TreeString t) {
175
return t == null ? null : t.toString();
179
* Creates a {@link TreeString}. Useful if you need to create one-off
180
* {@link TreeString} without {@link TreeStringBuilder}. Memory consumption
181
* is still about the same to {@code new String(s)}.
183
* @return null if the parameter is null
185
public static TreeString of(final String s) {
189
return new TreeString(null, s);
193
* Default {@link Converter} implementation for XStream that does interning
194
* scoped to one unmarshalling.
196
@SuppressWarnings("all")
197
public static final class ConverterImpl implements Converter {
198
public ConverterImpl(final XStream xs) {}
200
public void marshal(final Object source, final HierarchicalStreamWriter writer,
201
final MarshallingContext context) {
202
writer.setValue(source == null ? null : source.toString());
205
public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) {
206
TreeStringBuilder builder = (TreeStringBuilder)context.get(TreeStringBuilder.class);
207
if (builder == null) {
208
context.put(TreeStringBuilder.class, builder = new TreeStringBuilder());
211
final TreeStringBuilder _builder = builder;
212
context.addCompletionCallback(new Runnable() {
218
return builder.intern(reader.getValue());
221
public boolean canConvert(final Class type) {
222
return type == TreeString.class;