7 | * WARNING! Bundled indexing implemetation is quite resource intensive, creates lots of key-value pairs in redis
8 | * and negatively affects performance. Use it at your own risk. See below for description
9 | *
10 | *
11 | * Basically, the algorithm is as follows:
12 | *
13 | *
take incoming key, value and vertex
14 | *
calculate Metaphone for incoming value
15 | *
add vertex id to key:metaphone_of_value
16 | *
17 | *
18 | *
19 | * E.g. you have a vertex with id=123. It has a property "name" equal to "John Smith". It means:
20 | *
21 | *
key="name", value="John Smith"
22 | *
metaphone for John Smith is JNSM0
23 | *
123 will be added to name:JNSM0
24 | *
25 | *
26 | *
27 | * Of course, reality is slightly more complicated than that:
28 | *
29 | *
there are automatic and manual indices
30 | *
indices are accessed by their names, there can be several automatic and several manual indices on a
31 | * database
32 | *
an index may index vertices or edges, but not both
33 | *
indices may index all keys or only a subset of keys
34 | *
indices must persist and be accessible by their name and for the same set of keys as before after a
35 | * database restart
36 | *
37 | * So, we have to:
38 | *
39 | *
create a separate index entry for each auto/manual... with separate subentries for vertices and edges
40 | *
41 | *
42 | *
for each index key in an index save that key
43 | *
44 | *
for each key save vertex/edge ids the key points to
45 | *
46 | *
47 | *
for each index entry save list of keys that it holds
48 | *
49 | *
50 | *
save a list of all indices
51 | *
52 | * Sounds like a lot of work and, well, it is. Since redis is a key-value only store, there's a lot of extra work
53 | * that you have to do to make all this work.
54 | *
55 | *
Indices are stored in keys that all start with an index:...
56 | *
index:auto - prefix for all automatic indices
57 | *
index:manual - prefix for all manual indices
58 | *
59 | * For all of the above:
60 | *
index:type:index_name, where index_name
61 | * is a user-supplied index name - prefix for index data for index_name
62 | *
63 | *
index:type:index_name:key_name,
64 | * where key_name
65 | * is a user-supplied key name - prefix for index data for key_name key
66 | *
67 | *
index:type:index_name:key_name:metaphone,
68 | * where metaphone
69 | * is a calculated metaphone for a value - contains a list of vertex ids that have a key key_name
70 | * with a value whose metaphone matches metaphone
71 | *
72 | *
73 | *
index:meta:indices:auto - a list of automatic indices
74 | *
index:meta:indices:manual - a list of manual indices
75 | *
index:meta:auto - prefix for all meta information on automatic indices
76 | *
index:meta:manual - prefix for all meta information on manual indices
77 | *
78 | * For index:meta:(auto|manual) above:
79 | *
index:meta:type:index_name:class, where index_name
80 | * is a user-supplied index name - contains what class the index works on: vertex, edge, etc.
81 | *
82 | *
index:meta:type:index_name:keys, where index_name
83 | * is a user-supplied index name - contains a list of keys for this index
84 | *
85 | *
86 | *
87 | * So, as you see, indexing support manipulates quite a few values, so if speed is your primary concern, do not tur on
88 | * default indexing
89 | *
90 | *
91 | * @since 0.2
92 | * @since 0.3
93 | * @see A fast, fuzzy, full-text index using Redis
94 | */
95 | package com.tinkerpop.blueprints.pgm.impls.blueredis.index;
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | This is a straightforward and rather naïve implementation of a [Blueprints](http://blueprints.tinkerpop.com/)-enabled
2 | graph over [Redis](http://code.google.com/p/redis/).
3 |
4 | Blueprints is a database-agnostic library for handling graphs. Blueredis allows you to run Blueprints on top of redis.
5 | Also see [Pipes](http://pipes.tinkerpop.com/) and [Gremlin](http://gremlin.tinkerpop.com/) to see what this integration
6 | will also allow you to do.
7 |
8 | Running
9 | ===
10 |
11 | Requires Redis 2.x compatible version of [JRedis](http://github.com/alphazero/jredis) (git clone this repo, and you'll get a jar)
12 |
13 |
14 | Graph db = new RedisGraph();
15 |
16 | String password = "pass";
17 | Graph db1 = new RedisGraph(password);
18 |
19 | String host = "127.0.0.1";
20 | int port = 6379;
21 | Graph db2 = new RedisGraph(host, port);
22 |
23 | int database = 10;
24 | Graph db3 = new RedisGraph(host, port, pass, database);
25 |
26 | After this you work with the database as with any Blueprints-enabled graph.
27 |
28 | Peculiarities
29 | ===
30 |
31 | ID creation
32 | ---
33 |
34 | RedisGraph **handles id creation for you**. Any id parameter passed to addVertex/addEdge will be ignored. All ids are
35 | of type long
36 |
37 | Serializing properties (off by default)
38 | ---
39 |
40 | **By default all properties are saved and retrieved as strings**, regardless of the type of object you pass to
41 | `addVertex`. If you want to save actual objects/values, call `graph.serializeProperties(true)`. Note though, that it
42 | uses Base64 encoding on top of Java serialization so it may take up a lot of space.
43 |
44 | Transactions
45 | ---
46 |
47 | Currently **transactions are not supported**
48 |
49 | Indexing (off by default)
50 | ===
51 |
52 | **WARNING!** Indexing support that's bundled with Blueredis is rather resource intensive, manipulates quite a few
53 | key-value pair in redis and negatively affects performance. If speed is your primary concern, don't use the default
54 | indexing implementation
55 |
56 | Blueredis implements an index for both vertex and edge properties. This implementation is based on
57 | [A fast, fuzzy, full-text index using Redis](http://playnice.ly/blog/2010/05/05/a-fast-fuzzy-full-text-index-using-redis/).
58 |
59 | Turning indexing on
60 | ---
61 |
62 | **By default indexing is off**. To turn it on, call `graph.setIndexing(true)`. To turn it back on, call `graph.setIndexing(false)`.
63 |
64 | Implementing your own
65 | ---
66 |
67 | You may wish to implement your own indexing service. To do this:
68 |
69 | * implement Blueprints' `Index` and `AutomaticIndex` interfaces
70 | * override Blueredis' `RedisIndexManager` class
71 | * call `graph.setIndexing(true, yourManagerInstance)` and pass an instance of your indexManager to it
72 |
73 | Implementation
74 | ===
75 |
76 | Vertices
77 | ---
78 |
79 | Each vertex is represented by the following keys:
80 |
81 | * `vertex:ID`, vertex id. This one is used to test if a vertex exists
82 | * `vertex:ID:properties`, a hash of all vertex properties
83 | * `vertex:ID:edges:in`, a set of all incoming edges
84 | * `vertex:ID:edges:out`, a set of all outgoing edges
85 |
86 | Edges
87 | ---
88 |
89 | Each edge is represented by the following keys:
90 |
91 | * `edge:ID`, edge id. This one is used to test if an edge exists
92 | * `edge:ID:label`, a string containing the edge's label
93 | * `edge:ID:in`, index of "in" vertex
94 | * `edge:ID:out`, index of "out" vertex
95 | * `edge:ID:properties`, a hash of all edge properties
96 |
97 | Globals
98 | ---
99 |
100 | This are just some keys that hold values necessary for Blueredis to work:
101 |
102 | * `globals:next_vertex_id`, a counter that's incremented each time a new vertex is added
103 | * `globals:next_edge_id`, a counter that's incremented each time a new edge is added
104 | * `globals:vertices`, a sorted list of all vertex ids
105 | * `globals:edges`, a sorted list of all edge ids
106 |
107 | Indices
108 | ---
109 | **This section describes default indexing support that's bundled with Blueredis**. You're free to implement your own.
110 |
111 | Indices are stored in keys that all start with an `index:...`
112 |
113 | * `index:auto` - prefix for all automatic indices
114 | * `index:manual` - prefix for all manual indices
115 | For all of the above:
116 | * `index:type:index_name`, where `index_name` is a user-supplied index name - prefix for index data for `index_name`
117 | * `index:type:index_name:key_name`, where `key_name` is a user-supplied key name - prefix for index data for `key_name` key
118 | * `index:type:index_name:key_name:metaphone`, where `metaphone` is a calculated metaphone for a value - contains
119 | a list of vertex ids that have a key key_name with a value whose metaphone matches metaphone
120 | * `index:meta:indices:auto` - a list of automatic indices
121 | * `index:meta:indices:manual` - a list of manual indices
122 | * `index:meta:auto` - prefix for all meta information on automatic indices
123 | * `index:meta:manual` - prefix for all meta information on manual indices
124 | For index:meta:(auto|manual) above:
125 | * `index:meta:type:index_name:class`, where `index_name` is a user-supplied index name - contains what class
126 | the index works on: vertex, edge, etc.
127 | * `index:meta:type:index_name:keys`, where `index_name` is a user-supplied index name - contains a list of keys
128 | for this index
129 |
130 | Tests
131 | ===
132 |
133 | RedisGraph now passes tinkerpop's tests.
134 |
135 |
136 | Benchmarks
137 | ===
138 |
139 | An old benchmark can be found here: [benchmarks page in wiki](http://github.com/dmitriid/blueredis/wiki). Note that reeds
140 | seems to perform better under load than during singular requests. A newer benchmark will be published as soon as there's one :)
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 | com.dmitriid
7 | blueredis
8 | 0.2.1
9 | jar
10 | http://dmitriid.com
11 | Blueprints-enabled graph over redis
12 | Blueprints-enabled graph over redis.
13 |
14 |
15 | Dmitrii 'Mamut' Dimandt
16 | dmitrii@dmitriid.com
17 | http://dmitriid.com
18 |
19 |
20 | 2010
21 |
22 |
27 |
28 | com.tinkerpop
29 | blueprints
30 | 0.4
31 |
32 |
33 | commons-codec
34 | commons-codec
35 | 1.3
36 |
37 |
38 | jredis
39 | org.jredis.ri
40 | a.0-SNAPSHOT-jar-with-dependencies
41 | system
42 | ${basedir}/deps/JRedis.jar
43 |
44 |
45 |
46 | junit
47 | junit
48 | 4.5
49 | test
50 |
51 |
52 |
53 | log4j
54 | log4j
55 | 1.2.14
56 |
57 |
58 | org.slf4j
59 | slf4j-log4j12
60 | 1.6.1
61 |
62 |
63 |
64 | UTF-8
65 |
66 |
67 |
68 | maven repository
69 | http://mvnrepository.com
70 |
71 |
72 | tinkerpop-repository
73 | TinkerPop Maven2 Repository
74 | http://tinkerpop.com/maven2
75 |
76 | true
77 | daily
78 |
79 |
80 |
81 |
82 | ${basedir}/target
83 | ${artifactId}-${version}
84 |
85 | ${basedir}/src/main/java
86 |
87 | ${basedir}/src/test/java
88 |
89 | ${basedir}/target/classes
90 |
91 | ${basedir}/target/test-classes
92 |
93 |
94 |
95 | ${basedir}/src/main/resources
96 |
97 |
98 |
99 |
100 |
101 | ${basedir}/src/test/resources
102 |
103 |
104 |
105 |
106 |
107 | maven-compiler-plugin
108 |
109 | 1.6
110 | 1.6
111 |
112 |
113 |
114 | maven-assembly-plugin
115 | 2.2-beta-4
116 |
117 |
118 | package
119 |
120 | attached
121 |
122 |
123 |
124 |
125 |
126 | src/assembly/standalone.xml
127 | src/assembly/distribution.xml
128 |
129 | blueredis-${project.version}
130 | target
131 | target/assembly/work
132 | warn
133 |
138 |
139 |
140 |
141 | org.apache.maven.plugins
142 | maven-surefire-plugin
143 | 2.4.2
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 | testRedisGraph
152 | true
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 | org.apache.maven.wagon
161 | wagon-ftp
162 | 1.0-alpha-6
163 |
164 |
165 |
166 |
167 |
--------------------------------------------------------------------------------
/src/main/java/com/tinkerpop/blueprints/pgm/impls/blueredis/RedisElement.java:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Copyright (c) 2010-2011. Dmitrii Dimandt *
3 | * *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); *
5 | * you may not use this file except in compliance with the License. *
6 | * You may obtain a copy of the License at *
7 | * *
8 | * http://www.apache.org/licenses/LICENSE-2.0 *
9 | * *
10 | * Unless required by applicable law or agreed to in writing, software *
11 | * distributed under the License is distributed on an "AS IS" BASIS, *
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13 | * See the License for the specific language governing permissions and *
14 | * limitations under the License. *
15 | ******************************************************************************/
16 |
17 | package com.tinkerpop.blueprints.pgm.impls.blueredis;
18 |
19 | import com.tinkerpop.blueprints.pgm.impls.blueredis.index.RedisAutomaticIndex;
20 | import com.tinkerpop.blueprints.pgm.impls.blueredis.utils.Base64Coder;
21 | import com.tinkerpop.blueprints.pgm.Edge;
22 | import com.tinkerpop.blueprints.pgm.Element;
23 | import org.jredis.RedisException;
24 |
25 | import java.io.*;
26 | import java.util.HashSet;
27 | import java.util.List;
28 | import java.util.Set;
29 |
30 |
31 | public class RedisElement implements Element {
32 | protected Long id = null;
33 | protected RedisGraph graph = null;
34 |
35 | RedisElement(RedisGraph graph, Long id) {
36 | this.graph = graph;
37 | this.id = id;
38 | }
39 |
40 | @Override
41 | public Object getProperty(String s) {
42 | try {
43 | byte[] o = (byte[]) graph.getDatabase().hget(getIdentifier("properties"), s);
44 |
45 | if(o != null){
46 | if(graph.serializeProperties()){
47 | return getObject(new String(o));
48 | } else {
49 | return new String(o);
50 | }
51 | }
52 | } catch(RedisException e) {
53 | e.printStackTrace();
54 | } catch(IOException e) {
55 | e.printStackTrace();
56 | } catch(ClassNotFoundException e) {
57 | e.printStackTrace();
58 | }
59 | return null;
60 | }
61 |
62 | @Override
63 | public Set getPropertyKeys() {
64 | try {
65 | List l = graph.getDatabase().hkeys(getIdentifier("properties"));
66 | if(l != null){
67 | return new HashSet(l);
68 | }
69 | } catch(RedisException e) {
70 | e.printStackTrace();
71 | }
72 | return null;
73 | }
74 |
75 | @Override
76 | public void setProperty(String s, Object o) {
77 | try {
78 | String val;
79 | if(graph.serializeProperties()){
80 | val = writeObject(o);
81 | } else {
82 | val = String.valueOf(o);
83 | }
84 | if(graph.doIndexing()){
85 | Object oldValue = this.getProperty(s);
86 | for (RedisAutomaticIndex index : this.graph.getAutoIndices()) {
87 | index.autoUpdate(s, o, oldValue, this);
88 | }
89 | }
90 | graph.getDatabase().hset(getIdentifier("properties"), s, val);
91 | } catch(RedisException e) {
92 | e.printStackTrace();
93 | } catch(IOException e) {
94 | e.printStackTrace();
95 | }
96 | }
97 |
98 | @Override
99 | public Object removeProperty(String s) {
100 | try {
101 | Object property = getProperty(s);
102 | if(graph.doIndexing()) {
103 | for (RedisAutomaticIndex index : this.graph.getAutoIndices()) {
104 | index.autoRemove(s, property, this);
105 | }
106 | }
107 | graph.getDatabase().hdel(getIdentifier("properties"), s);
108 | return property;
109 | } catch(RedisException e) {
110 | e.printStackTrace();
111 | }
112 | return null;
113 | }
114 |
115 | @Override
116 | public Object getId() {
117 | return id;
118 | }
119 |
120 | public void remove() {
121 | if(this instanceof RedisVertex) {
122 | RedisVertex vertex = (RedisVertex) this;
123 |
124 | // 1. Remove all edges
125 | // 2. Remove self
126 | // 3. Remove from global registry
127 |
128 | Iterable edges;
129 |
130 | edges = vertex.getInEdges();
131 | for(Edge edge : edges) {
132 | ((RedisEdge) edge).remove();
133 | }
134 |
135 | edges = vertex.getOutEdges();
136 | for(Edge edge : edges) {
137 | ((RedisEdge) edge).remove();
138 | }
139 |
140 | try {
141 | graph.getDatabase().del(getIdentifier(null));
142 | graph.getDatabase().del(getIdentifier("properties"));
143 | graph.getDatabase().del(getIdentifier("edges:in"));
144 | graph.getDatabase().del(getIdentifier("edges:out"));
145 | graph.getDatabase().del("vertex:" + String.valueOf(id));
146 | graph.getDatabase().zrem("globals:vertices", String.valueOf(id));
147 |
148 | } catch(RedisException e) {
149 | e.printStackTrace();
150 | }
151 | } else {
152 | RedisEdge edge = (RedisEdge) this;
153 |
154 | // 1. Remove all vertices
155 | // 2. Remove self
156 | // 3. Remove from global registry
157 |
158 | RedisVertex in = (RedisVertex) edge.getInVertex();
159 | RedisVertex out = (RedisVertex) edge.getOutVertex();
160 |
161 | try {
162 | graph.getDatabase().del(getIdentifier("in"));
163 | graph.getDatabase().del(getIdentifier("out"));
164 | graph.getDatabase().del(getIdentifier("label"));
165 | graph.getDatabase().del(getIdentifier("properties"));
166 | graph.getDatabase().del("edge:" + String.valueOf(id));
167 |
168 | graph.getDatabase().zrem(out.getIdentifier("edges:out"), String.valueOf(getId()));
169 | graph.getDatabase().zrem(in.getIdentifier("edges:in"), String.valueOf(getId()));
170 |
171 | graph.getDatabase().zrem("globals:edges", String.valueOf(id));
172 | } catch(RedisException e) {
173 | e.printStackTrace();
174 | }
175 |
176 | }
177 | }
178 |
179 | protected String getIdentifier(String suffix) {
180 | String prefix = this instanceof RedisVertex ? "vertex:" : "edge:";
181 | String identifier = prefix + String.valueOf(id);
182 | if(suffix != null) identifier = identifier + ":" + suffix;
183 |
184 | return identifier;
185 |
186 | }
187 |
188 | public int hashCode() {
189 | return this.getId().hashCode();
190 | }
191 |
192 | public boolean equals(Object object) {
193 | return (this.getClass().equals(object.getClass()) && this.getId().equals(((Element) object).getId()));
194 | }
195 |
196 | private String writeObject(Object o) throws IOException {
197 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
198 | ObjectOutputStream oos = new ObjectOutputStream(baos);
199 |
200 | oos.writeObject(o);
201 | oos.close();
202 |
203 | return new String(Base64Coder.encode(baos.toByteArray()));
204 | }
205 |
206 | private Object getObject(String s) throws IOException, ClassNotFoundException {
207 | byte[] data = Base64Coder.decode(s);
208 | ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
209 | Object o = ois.readObject();
210 | ois.close();
211 | return o;
212 | }
213 |
214 | }
215 |
--------------------------------------------------------------------------------
/src/main/java/com/tinkerpop/blueprints/pgm/impls/blueredis/index/RedisIndex.java:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Copyright (c) 2010-2011. Dmitrii Dimandt *
3 | * *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); *
5 | * you may not use this file except in compliance with the License. *
6 | * You may obtain a copy of the License at *
7 | * *
8 | * http://www.apache.org/licenses/LICENSE-2.0 *
9 | * *
10 | * Unless required by applicable law or agreed to in writing, software *
11 | * distributed under the License is distributed on an "AS IS" BASIS, *
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13 | * See the License for the specific language governing permissions and *
14 | * limitations under the License. *
15 | ******************************************************************************/
16 |
17 |
18 | package com.tinkerpop.blueprints.pgm.impls.blueredis.index;
19 |
20 | import com.tinkerpop.blueprints.pgm.Element;
21 | import com.tinkerpop.blueprints.pgm.Index;
22 | import com.tinkerpop.blueprints.pgm.impls.blueredis.RedisEdge;
23 | import com.tinkerpop.blueprints.pgm.impls.blueredis.RedisGraph;
24 | import com.tinkerpop.blueprints.pgm.impls.blueredis.RedisVertex;
25 | import org.apache.commons.codec.language.DoubleMetaphone;
26 | import org.jredis.JRedis;
27 | import org.jredis.RedisException;
28 |
29 | import java.text.Normalizer;
30 | import java.util.ArrayList;
31 | import java.util.HashSet;
32 | import java.util.List;
33 | import java.util.Set;
34 | import java.util.regex.Matcher;
35 | import java.util.regex.Pattern;
36 |
37 | /**
38 | * @see RedisAutomaticIndex
39 | * @see RedisIndexManager
40 | */
41 |
42 | public class RedisIndex implements Index {
43 |
44 | protected RedisGraph graph;
45 | protected JRedis database;
46 |
47 | protected String indexName;
48 | protected Class indexClass;
49 | protected String nodeName;
50 |
51 | protected Set indexKeys = new HashSet();
52 |
53 | protected DoubleMetaphone metaphone = new DoubleMetaphone();
54 |
55 | protected boolean indexAll = true;
56 |
57 | protected Type indexType = Type.MANUAL;
58 |
59 | public RedisIndex(RedisGraph graph, final String indexName, final Class indexClass) {
60 | this.graph = graph;
61 | this.database = graph.getDatabase();
62 | this.indexClass = indexClass;
63 |
64 | // convert any index name to a form that can be sent to redis, i.e.
65 | // convert "Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ" to "this_is_a_funky_string"
66 | this.indexName = RedisIndex.normalizeName(indexName);
67 | this.nodeName = RedisIndexKeys.MANUAL + this.indexName + ":";
68 | this.metaphone.setMaxCodeLen(12);
69 | }
70 | public RedisIndex(RedisGraph graph, final String indexName, final Class indexClass, Set indexKeys) {
71 | this(graph, indexName, indexClass);
72 | this.indexKeys = indexKeys;
73 | }
74 |
75 | @Override
76 | public String getIndexName() {
77 | return this.indexName;
78 | }
79 |
80 | @Override
81 | public Class getIndexClass() {
82 | return this.indexClass;
83 | }
84 |
85 | @Override
86 | public Type getIndexType() {
87 | return this.indexType;
88 | }
89 |
90 | @Override
91 | public void put(final String key, final Object value, final T element) {
92 | if(!this.indexAll && !this.indexKeys.contains(key)) {
93 | return;
94 | }
95 |
96 | if (!this.indexClass.isAssignableFrom(element.getClass())) {
97 | return;
98 | }
99 |
100 | indexKeys.add(key);
101 |
102 | String val = getMetaphone(value);
103 | String node_name = this.nodeName + key + ":" + val;
104 |
105 | try {
106 | database.sadd(node_name, element.getId().toString());
107 | } catch(RedisException e) {
108 | e.printStackTrace();
109 | }
110 |
111 | if(this.indexAll){
112 | this.updateIndexKey(key);
113 | }
114 | }
115 |
116 | @Override
117 | public Iterable get(String key, Object value) {
118 | if(!indexKeys.contains(key)){
119 | return new ArrayList();
120 | }
121 | ArrayList arr = new ArrayList();
122 |
123 | String val = getMetaphone(value);
124 | String node_name;
125 | List l;
126 |
127 | try {
128 | node_name = this.nodeName + key + ":" + val;
129 | l = database.smembers(node_name);
130 | if(l != null) {
131 | for(byte[] o : l) {
132 | Long idx = Long.parseLong(new String(o));
133 | if(this.indexClass.isAssignableFrom(RedisVertex.class)){
134 | arr.add((T)new RedisVertex(idx, graph));
135 | } else {
136 | arr.add((T)new RedisEdge(idx, graph));
137 | }
138 | }
139 | }
140 | } catch(RedisException e) {
141 | e.printStackTrace();
142 | }
143 |
144 | return arr;
145 | }
146 |
147 | @Override
148 | public void remove(String key, Object value, T element) {
149 | if(value == null) return;
150 |
151 | if (!this.indexClass.isAssignableFrom(element.getClass())) {
152 | return;
153 | }
154 |
155 | String val = getMetaphone(value);
156 | String node_name = this.nodeName + key + ":" + val;
157 |
158 | try {
159 | database.srem(node_name, element.getId().toString());
160 | } catch(RedisException e) {
161 | e.printStackTrace();
162 | }
163 | }
164 |
165 | public void removeElement(Element element){
166 | if (!this.indexClass.isAssignableFrom(element.getClass())) {
167 | return;
168 | }
169 |
170 | // note: this may be VERY resource intensive (especially memory-wise)
171 | // this implementation retrieves all values from each key and attempts
172 | // to remove the current element from the key
173 |
174 | for(final String key : this.indexKeys){
175 | try {
176 | List keys = database.keys(this.nodeName + "*");
177 |
178 | for(final String val : keys){
179 | try{
180 | database.srem(val, element.getId().toString());
181 | } catch (RedisException ignore) {
182 | }
183 | }
184 | } catch (RedisException ignore) {
185 | }
186 | }
187 | }
188 |
189 | public void autoUpdate(final String key, final Object newValue, final Object oldValue, final T element) {
190 | if (this.getIndexClass().isAssignableFrom(element.getClass()) && (this.indexAll || this.indexKeys.size() == 0 || this.indexKeys.contains(key))) {
191 | if (oldValue != null)
192 | this.remove(key, oldValue, element);
193 | this.put(key, newValue, element);
194 | if(this.indexAll){
195 | this.updateIndexKey(key);
196 | }
197 | }
198 | }
199 |
200 | public void autoRemove(final String key, final Object oldValue, final T element) {
201 | if (this.getIndexClass().isAssignableFrom(element.getClass()) && (this.indexAll || this.indexKeys.size() == 0 || this.indexKeys.contains(key))) {
202 | this.remove(key, oldValue, element);
203 | }
204 | }
205 |
206 | protected void updateIndexKey(String key){
207 | try{
208 | if(this.indexType.equals(Type.AUTOMATIC)){
209 | database.sadd(RedisIndexKeys.META_AUTO + this.indexName + ":keys", key);
210 | } else {
211 | database.sadd(RedisIndexKeys.META_MANUAL + this.indexName + ":keys", key);
212 | }
213 | } catch (RedisException ignore) {
214 | }
215 |
216 | }
217 |
218 | private String getMetaphone(Object o){
219 | String val = "";
220 | try{
221 | val = (String) metaphone.encode(String.valueOf(o));
222 | } catch(Exception ignored) {
223 | }
224 |
225 | if(val.equals("")) val = o.toString();
226 |
227 | return val;
228 | }
229 |
230 | public static String normalizeName(final String indexName){
231 | return Normalizer.normalize(indexName, Normalizer.Form.NFD)
232 | .replaceAll("[^\\p{ASCII}]", "")
233 | .replaceAll(" ", "_")
234 | .toLowerCase();
235 | }
236 |
237 | public void addKey(String key){
238 | this.indexKeys.add(key);
239 | this.updateIndexKey(key);
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/src/main/java/com/tinkerpop/blueprints/pgm/impls/blueredis/index/RedisIndexManager.java:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Copyright (c) 2010-2011. Dmitrii Dimandt *
3 | * *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); *
5 | * you may not use this file except in compliance with the License. *
6 | * You may obtain a copy of the License at *
7 | * *
8 | * http://www.apache.org/licenses/LICENSE-2.0 *
9 | * *
10 | * Unless required by applicable law or agreed to in writing, software *
11 | * distributed under the License is distributed on an "AS IS" BASIS, *
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13 | * See the License for the specific language governing permissions and *
14 | * limitations under the License. *
15 | ******************************************************************************/
16 |
17 | package com.tinkerpop.blueprints.pgm.impls.blueredis.index;
18 |
19 | import com.tinkerpop.blueprints.pgm.*;
20 | import com.tinkerpop.blueprints.pgm.impls.blueredis.RedisEdge;
21 | import com.tinkerpop.blueprints.pgm.impls.blueredis.RedisGraph;
22 | import com.tinkerpop.blueprints.pgm.impls.blueredis.RedisVertex;
23 | import com.tinkerpop.blueprints.pgm.util.AutomaticIndexHelper;
24 | import org.jredis.JRedis;
25 | import org.jredis.RedisException;
26 |
27 | import java.util.*;
28 |
29 | /**
30 | * @see RedisIndex
31 | * @see RedisAutomaticIndex
32 | */
33 |
34 | public class RedisIndexManager {
35 |
36 | protected RedisGraph graph = null;
37 | protected Map> manualIndices = new HashMap>();
38 | protected Map> autoIndices = new HashMap>();
39 | protected boolean restoreMode = false; // when in restore mode, don't save data back to db
40 |
41 | public RedisIndexManager() {
42 | }
43 |
44 | public void setGraph(RedisGraph graph) {
45 | this.graph = graph;
46 | }
47 |
48 | public Index createManualIndex(final String indexName, final Class indexClass){
49 | return this.createManualIndex(indexName, indexClass, new HashSet());
50 | }
51 | public Index createManualIndex(final String indexName, final Class indexClass, Set keys) {
52 | String idxName = RedisIndex.normalizeName(indexName);
53 | RedisIndex idx = (RedisIndex)this.manualIndices.get(idxName);
54 |
55 | if(null != idx){
56 | throw new RuntimeException("Index already exists: " + indexName);
57 | } else {
58 | idx = new RedisIndex(this.graph, idxName, indexClass, keys);
59 |
60 | this.manualIndices.put(idxName, (RedisIndex)idx);
61 | if(!this.restoreMode){
62 | this.saveIndexMeta(RedisIndexKeys.MANUAL, idxName, indexClass.getCanonicalName(), keys);
63 | }
64 | }
65 |
66 | return idx;
67 | }
68 |
69 | public AutomaticIndex createAutomaticIndex(final String indexName, final Class indexClass, Set keys){
70 | String idxName = RedisIndex.normalizeName(indexName);
71 | RedisAutomaticIndex idx = (RedisAutomaticIndex)this.autoIndices.get(idxName);
72 |
73 | if(null != idx){
74 | throw new RuntimeException("Index already exists: " + indexName);
75 | } else {
76 | idx = new RedisAutomaticIndex(this.graph, idxName, indexClass, keys);
77 |
78 | this.autoIndices.put(idxName, (AutomaticIndex)idx);
79 | if(!this.restoreMode){
80 | this.saveIndexMeta(RedisIndexKeys.AUTO, idxName, indexClass.getCanonicalName(), keys);
81 | }
82 | }
83 |
84 | return idx;
85 | }
86 |
87 | public Index getIndex(final String indexName, final Class indexClass) {
88 | String idxName = RedisIndex.normalizeName(indexName);
89 | Index index = this.manualIndices.get(idxName);
90 | if (null == index)
91 | index = this.autoIndices.get(idxName);
92 |
93 | if(null == index)
94 | throw new RuntimeException("No such index exists: " + indexName);
95 | if (!indexClass.isAssignableFrom(index.getIndexClass()))
96 | throw new RuntimeException(indexClass + " is not assignable from " + index.getIndexClass());
97 | else
98 | return (Index) index;
99 | }
100 |
101 | public Iterable> getIndices() {
102 | List> list = new ArrayList>();
103 | for (Index index : manualIndices.values()) {
104 | list.add(index);
105 | }
106 | for (Index index : autoIndices.values()) {
107 | list.add(index);
108 | }
109 | return list;
110 | }
111 |
112 | public void dropIndex(final String indexName) {
113 | String idxName = RedisIndex.normalizeName(indexName);
114 | JRedis db = this.graph.getDatabase();
115 |
116 | try {
117 | List keyList = db.keys(RedisIndexKeys.AUTO + idxName + ":*");
118 | for(String key : keyList){
119 | db.del(key);
120 | }
121 |
122 | keyList = db.keys(RedisIndexKeys.META_AUTO + idxName + ":*");
123 | for(String key : keyList){
124 | db.del(key);
125 | }
126 |
127 | db.lrem(RedisIndexKeys.META_INDICES_AUTO, idxName, 0);
128 |
129 | keyList = db.keys(RedisIndexKeys.MANUAL + idxName + ":*");
130 | for(String key : keyList){
131 | db.del(key);
132 | }
133 |
134 | keyList = db.keys(RedisIndexKeys.META_MANUAL + idxName + ":*");
135 | for(String key : keyList){
136 | db.del(key);
137 | }
138 |
139 | db.lrem(RedisIndexKeys.META_INDICES_MANUAL, idxName, 0);
140 | } catch (RedisException e) {
141 | e.printStackTrace();
142 | }
143 |
144 | this.manualIndices.remove(idxName);
145 | this.autoIndices.remove(idxName);
146 |
147 | }
148 |
149 | public Iterable getAutoIndices() {
150 | List list = new ArrayList();
151 | for (Index index : autoIndices.values()) {
152 | list.add((RedisAutomaticIndex)index);
153 | }
154 | return list;
155 | }
156 |
157 | public Iterable getManualIndices() {
158 | List list = new ArrayList();
159 | for (Index index : manualIndices.values()) {
160 | list.add((RedisIndex)index);
161 | }
162 | return list;
163 | }
164 |
165 | private void saveIndexMeta(final String type, final String indexName, final String className, Set keys){
166 | JRedis db = this.graph.getDatabase();
167 |
168 | try {
169 | if (type.equals(RedisIndexKeys.AUTO)) {
170 | db.lpush(RedisIndexKeys.META_INDICES_AUTO, indexName);
171 | db.set(RedisIndexKeys.META_AUTO + indexName + ":class", className);
172 | if(null != keys){
173 | String key_list = RedisIndexKeys.META_AUTO + indexName + ":keys";
174 | for(String key : keys){
175 | db.sadd(key_list, key);
176 | }
177 | }
178 | } else {
179 | db.lpush(RedisIndexKeys.META_INDICES_MANUAL, indexName);
180 | db.set(RedisIndexKeys.META_MANUAL + indexName + ":class", className);
181 | }
182 | } catch (RedisException e) {
183 | e.printStackTrace();
184 | }
185 | }
186 |
187 | public void restoreIndices(){
188 | this.restoreMode = true;
189 | this.restoreIndices(RedisIndexKeys.AUTO);
190 | this.restoreIndices(RedisIndexKeys.MANUAL);
191 | this.restoreMode = false;
192 | }
193 |
194 | public void restoreIndices(String type){
195 | JRedis db = this.graph.getDatabase();
196 |
197 | String metaIndices = type.equals(RedisIndexKeys.AUTO) ? RedisIndexKeys.META_INDICES_AUTO : RedisIndexKeys.META_INDICES_MANUAL;
198 | String metaType = type.equals(RedisIndexKeys.AUTO) ? RedisIndexKeys.META_AUTO : RedisIndexKeys.META_MANUAL;
199 |
200 | try {
201 | List indices = db.lrange(metaIndices, 0, db.llen(metaIndices));
202 |
203 | if(null == indices) return;
204 |
205 | for(byte[] idx: indices){
206 | String indexName = new String(idx);
207 |
208 | String className = new String(db.get(metaType + indexName + ":class"));
209 |
210 | Class indexClass = Class.forName(className);
211 |
212 | List keys = db.smembers(metaType + indexName + ":keys");
213 | HashSet indexKeys = new HashSet();
214 |
215 | if(null != keys){
216 | for(byte[] key : keys){
217 | indexKeys.add(new String(key));
218 | }
219 | }
220 |
221 | if(type.equals(RedisIndexKeys.AUTO)){
222 | this.createAutomaticIndex(indexName, indexClass, indexKeys.size() != 0 ? indexKeys : null);
223 | } else {
224 | this.createManualIndex(indexName, indexClass, indexKeys.size() != 0 ? indexKeys : new HashSet());
225 | }
226 | }
227 | } catch (RedisException e) {
228 | e.printStackTrace();
229 | }catch (ClassNotFoundException e) {
230 | e.printStackTrace();
231 | }
232 | }
233 |
234 | public void removeElement(Element el){
235 | AutomaticIndexHelper.removeElement(this.graph, el);
236 | for (Index index : this.getManualIndices()) {
237 | if (Vertex.class.isAssignableFrom(index.getIndexClass())) {
238 | RedisIndex idx = (RedisIndex) index;
239 | idx.removeElement(el);
240 | } else if(Edge.class.isAssignableFrom(index.getIndexClass())) {
241 | RedisIndex idx = (RedisIndex) index;
242 | idx.removeElement(el);
243 | }
244 | }
245 | }
246 |
247 | public void clear(){
248 | this.autoIndices.clear();
249 | this.manualIndices.clear();
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/src/main/java/com/tinkerpop/blueprints/pgm/impls/blueredis/RedisGraph.java:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Copyright (c) 2010-2011. Dmitrii Dimandt *
3 | * *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); *
5 | * you may not use this file except in compliance with the License. *
6 | * You may obtain a copy of the License at *
7 | * *
8 | * http://www.apache.org/licenses/LICENSE-2.0 *
9 | * *
10 | * Unless required by applicable law or agreed to in writing, software *
11 | * distributed under the License is distributed on an "AS IS" BASIS, *
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13 | * See the License for the specific language governing permissions and *
14 | * limitations under the License. *
15 | ******************************************************************************/
16 |
17 | package com.tinkerpop.blueprints.pgm.impls.blueredis;
18 |
19 | import com.tinkerpop.blueprints.pgm.*;
20 | import com.tinkerpop.blueprints.pgm.impls.blueredis.index.RedisAutomaticIndex;
21 | import com.tinkerpop.blueprints.pgm.impls.blueredis.index.RedisIndex;
22 | import com.tinkerpop.blueprints.pgm.impls.blueredis.index.RedisIndexManager;
23 | import com.tinkerpop.blueprints.pgm.impls.blueredis.iterators.RedisEdgeIterable;
24 | import com.tinkerpop.blueprints.pgm.impls.blueredis.iterators.RedisVertexIterable;
25 | import com.tinkerpop.blueprints.pgm.util.AutomaticIndexHelper;
26 | import org.jredis.JRedis;
27 | import org.jredis.RedisException;
28 | import org.jredis.ri.alphazero.JRedisClient;
29 |
30 | import java.util.*;
31 |
32 | public class RedisGraph implements IndexableGraph {
33 |
34 | private JRedis database = null;
35 | private boolean serializeProps = false; // if true, save type info with prop values
36 | private Index index;
37 | private boolean do_index = true;
38 | private RedisIndexManager indexManager;
39 |
40 | protected Map indices = new HashMap();
41 | protected Map autoIndices = new HashMap();
42 |
43 | public RedisGraph() {
44 | database = new JRedisClient();
45 | }
46 |
47 | public RedisGraph(String password) {
48 | database = new JRedisClient(password);
49 | }
50 |
51 | public RedisGraph(String host, int port) {
52 | database = new JRedisClient(host, port);
53 | }
54 |
55 | public RedisGraph(String host, int port, String password, int database) {
56 | this.database = new JRedisClient(host, port, password, database);
57 | }
58 |
59 | public void serializeProperties(boolean serialize) {
60 | serializeProps = serialize;
61 | }
62 |
63 | public boolean serializeProperties() {
64 | return serializeProps;
65 | }
66 |
67 | public void setIndexing(boolean b) {
68 | do_index = b;
69 | if(b == true){
70 | this.indexManager = new RedisIndexManager();
71 | this.prepareIndexing();
72 | }
73 | }
74 |
75 | public void setIndexing(boolean b, RedisIndexManager manager) {
76 | do_index = b;
77 | this.indexManager = manager;
78 | if(b == true){
79 | this.prepareIndexing();
80 | }
81 | }
82 |
83 | public boolean doIndexing(){
84 | return do_index;
85 | }
86 |
87 | public long nextVertexId(){
88 | try {
89 | return database.incr("globals:next_vertex_id");
90 | } catch(RedisException e) {
91 | e.printStackTrace();
92 | }
93 | return 0;
94 | }
95 |
96 | public long nextEdgeId(){
97 | try {
98 | return database.incr("globals:next_edge_id");
99 | } catch(RedisException e) {
100 | e.printStackTrace();
101 | }
102 | return 0;
103 | }
104 |
105 | public JRedis getDatabase(){
106 | return database;
107 | }
108 |
109 | @Override
110 | public Vertex addVertex(Object o) {
111 | final Vertex vertex = new RedisVertex(this);
112 | return vertex;
113 | }
114 |
115 | @Override
116 | public Vertex getVertex(Object o) {
117 | try{
118 | Long id = getLong(o);
119 |
120 | Object v = database.get("vertex:" + String.valueOf(id));
121 |
122 | if(v != null){
123 | final Vertex vertex = new RedisVertex(id, this);
124 | return vertex;
125 | }
126 |
127 | return null;
128 | } catch (Exception e){
129 | return null;
130 | }
131 | }
132 |
133 | @Override
134 | public void removeVertex(Vertex vertex) {
135 | if(this.doIndexing()) {
136 | this.indexManager.removeElement(vertex);
137 | }
138 | ((RedisElement)vertex).remove();
139 | }
140 |
141 | @Override
142 | public Iterable getVertices() {
143 | return new RedisVertexIterable(this);
144 | }
145 |
146 | @Override
147 | public Edge addEdge(Object o, Vertex outVertex, Vertex inVertex, String s) {
148 | final Edge edge = new RedisEdge((RedisVertex)inVertex, (RedisVertex)outVertex, s, this);
149 | return edge;
150 | }
151 |
152 | @Override
153 | public Edge getEdge(Object o) {
154 | try{
155 | Long id = getLong(o);
156 |
157 | Object e = database.get("edge:" + String.valueOf(id));
158 |
159 | if(e != null) {
160 | final Edge edge = new RedisEdge(id, this);
161 | return edge;
162 | }
163 | return null;
164 | } catch(Exception e){
165 | return null;
166 | }
167 | }
168 |
169 | @Override
170 | public void removeEdge(Edge edge) {
171 | if(this.doIndexing()) {
172 | this.indexManager.removeElement(edge);
173 | }
174 | ((RedisEdge) edge).remove();
175 | }
176 |
177 | @Override
178 | public Iterable getEdges() {
179 | return new RedisEdgeIterable(this);
180 | }
181 |
182 | @Override
183 | public void clear() {
184 | try {
185 | database.flushdb();
186 | if(this.doIndexing() && null != this.indexManager){
187 | this.indexManager.clear();
188 | // this is hackish in the following sense
189 | // if we've dropped automatic indices, *do not* recreate them on next start
190 | // we only automatically create indices for fresh databases
191 | this.database.set("indexing_set", 1);
192 | }
193 | } catch(RedisException e) {
194 | e.printStackTrace();
195 | }
196 | }
197 |
198 | @Override
199 | public void shutdown() {
200 | database.quit();
201 | }
202 |
203 | public static String getIdentifier(String prefix, Long id, String suffix) {
204 | String identifier = prefix + String.valueOf(id);
205 | if(suffix != null) identifier += ":" + suffix;
206 |
207 | return identifier;
208 | }
209 |
210 | public String toString() {
211 | try {
212 | Map info = this.database.info();
213 | return "redis[" + info.get("redis_version") + "]";
214 | } catch(RedisException e) {
215 | e.printStackTrace();
216 | }
217 | return "redis[error retrieving info]";
218 | }
219 |
220 | @Override
221 | public Index createManualIndex(final String indexName, final Class indexClass) {
222 | if(!this.doIndexing()){
223 | return null;
224 | }
225 | return this.indexManager.createManualIndex(indexName, indexClass);
226 | }
227 |
228 | @Override
229 | public AutomaticIndex createAutomaticIndex(final String indexName, final Class indexClass, Set keys) {
230 | if(!this.doIndexing()){
231 | return null;
232 | }
233 | return this.indexManager.createAutomaticIndex(indexName, indexClass, keys);
234 | }
235 |
236 | @Override
237 | public Index getIndex(final String indexName, final Class indexClass) {
238 | if(!this.doIndexing()){
239 | return null;
240 | }
241 | return this.indexManager.getIndex(indexName, indexClass);
242 | }
243 |
244 | @Override
245 | public Iterable> getIndices() {
246 | if(!this.doIndexing()){
247 | return new ArrayList>();
248 | }
249 | return this.indexManager.getIndices();
250 | }
251 |
252 | @Override
253 | public void dropIndex(final String indexName) {
254 | if(this.doIndexing()){
255 | this.indexManager.dropIndex(indexName);
256 | }
257 | }
258 |
259 | protected Iterable getAutoIndices() {
260 | if(!this.doIndexing()){
261 | return new ArrayList();
262 | }
263 | return this.indexManager.getAutoIndices();
264 | }
265 |
266 | protected Iterable getManualIndices() {
267 | if(!this.doIndexing()){
268 | return new ArrayList();
269 | }
270 | return this.indexManager.getManualIndices();
271 | }
272 |
273 | private void prepareIndexing(){
274 |
275 | this.indexManager.setGraph(this);
276 | this.indexManager.restoreIndices();
277 |
278 | try {
279 | Object indexing_set = this.database.get("indexing_set");
280 | if(null == indexing_set){
281 | this.createAutomaticIndex(Index.VERTICES, RedisVertex.class, null);
282 | this.createAutomaticIndex(Index.EDGES, RedisEdge.class, null);
283 | }
284 |
285 | this.database.set("indexing_set", 1);
286 | } catch (RedisException e) {
287 | e.printStackTrace();
288 | }
289 | }
290 |
291 | // see http://stackoverflow.com/questions/1302605/how-do-i-convert-from-int-to-long-in-java/2904999#2904999
292 | private final Long getLong(Object obj) throws IllegalArgumentException {
293 | Long rv;
294 |
295 | if((obj.getClass() == Integer.class) || (obj.getClass() == Long.class) || (obj.getClass() == Double.class)) {
296 | rv = Long.parseLong(obj.toString());
297 | } else if((obj.getClass() == int.class) || (obj.getClass() == long.class) || (obj.getClass() == double.class)) {
298 | rv = (Long) obj;
299 | } else if(obj.getClass() == String.class) {
300 | rv = Long.parseLong(obj.toString());
301 | } else {
302 | throw new IllegalArgumentException("getLong: type " + obj.getClass() + " = \"" + obj.toString() + "\" unaccounted for");
303 | }
304 |
305 | return rv;
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/src/main/java/biz/source_code/base64Coder/Base64Coder.java:
--------------------------------------------------------------------------------
1 | // Copyright 2003-2010 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
2 | // www.source-code.biz, www.inventec.ch/chdh
3 | //
4 | // This module is multi-licensed and may be used under the terms
5 | // of any of the following licenses:
6 | //
7 | // EPL, Eclipse Public License, http://www.eclipse.org/legal
8 | // LGPL, GNU Lesser General Public License, http://www.gnu.org/licenses/lgpl.html
9 | // AL, Apache License, http://www.apache.org/licenses
10 | // BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php
11 | //
12 | // Please contact the author if you need another license.
13 | // This module is provided "as is", without warranties of any kind.
14 |
15 | package biz.source_code.base64Coder;
16 |
17 | /**
18 | * A Base64 encoder/decoder.
19 | *
20 | *
21 | * This class is used to encode and decode data in Base64 format as described in RFC 1521.
22 | *
23 | *
24 | * Project home page: www.source-code.biz/base64coder/java
25 | * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
26 | * Multi-licensed: EPL / LGPL / AL / BSD.
27 | */
28 | public class Base64Coder {
29 |
30 | // The line separator string of the operating system.
31 | private static final String systemLineSeparator = System.getProperty("line.separator");
32 |
33 | // Mapping table from 6-bit nibbles to Base64 characters.
34 | private static char[] map1 = new char[64];
35 |
36 | static {
37 | int i = 0;
38 | for(char c = 'A'; c <= 'Z'; c++) map1[i++] = c;
39 | for(char c = 'a'; c <= 'z'; c++) map1[i++] = c;
40 | for(char c = '0'; c <= '9'; c++) map1[i++] = c;
41 | map1[i++] = '+';
42 | map1[i++] = '/';
43 | }
44 |
45 | // Mapping table from Base64 characters to 6-bit nibbles.
46 | private static byte[] map2 = new byte[128];
47 |
48 | static {
49 | for(int i = 0; i < map2.length; i++) map2[i] = -1;
50 | for(int i = 0; i < 64; i++) map2[map1[i]] = (byte) i;
51 | }
52 |
53 | /**
54 | * Encodes a string into Base64 format.
55 | * No blanks or line breaks are inserted.
56 | *
57 | * @param s A String to be encoded.
58 | * @return A String containing the Base64 encoded data.
59 | */
60 | public static String encodeString(String s) {
61 | return new String(encode(s.getBytes()));
62 | }
63 |
64 | /**
65 | * Encodes a byte array into Base 64 format and breaks the output into lines of 76 characters.
66 | * This method is compatible with sun.misc.BASE64Encoder.encodeBuffer(byte[]).
67 | *
68 | * @param in An array containing the data bytes to be encoded.
69 | * @return A String containing the Base64 encoded data, broken into lines.
70 | */
71 | public static String encodeLines(byte[] in) {
72 | return encodeLines(in, 0, in.length, 76, systemLineSeparator);
73 | }
74 |
75 | /**
76 | * Encodes a byte array into Base 64 format and breaks the output into lines.
77 | *
78 | * @param in An array containing the data bytes to be encoded.
79 | * @param iOff Offset of the first byte in in to be processed.
80 | * @param iLen Number of bytes to be processed in in, starting at iOff.
81 | * @param lineLen Line length for the output data. Should be a multiple of 4.
82 | * @param lineSeparator The line separator to be used to separate the output lines.
83 | * @return A String containing the Base64 encoded data, broken into lines.
84 | */
85 | public static String encodeLines(byte[] in, int iOff, int iLen, int lineLen, String lineSeparator) {
86 | int blockLen = (lineLen * 3) / 4;
87 | if(blockLen <= 0) throw new IllegalArgumentException();
88 | int lines = (iLen + blockLen - 1) / blockLen;
89 | int bufLen = ((iLen + 2) / 3) * 4 + lines * lineSeparator.length();
90 | StringBuilder buf = new StringBuilder(bufLen);
91 | int ip = 0;
92 | while(ip < iLen) {
93 | int l = Math.min(iLen - ip, blockLen);
94 | buf.append(encode(in, iOff + ip, l));
95 | buf.append(lineSeparator);
96 | ip += l;
97 | }
98 | return buf.toString();
99 | }
100 |
101 | /**
102 | * Encodes a byte array into Base64 format.
103 | * No blanks or line breaks are inserted in the output.
104 | *
105 | * @param in An array containing the data bytes to be encoded.
106 | * @return A character array containing the Base64 encoded data.
107 | */
108 | public static char[] encode(byte[] in) {
109 | return encode(in, 0, in.length);
110 | }
111 |
112 | /**
113 | * Encodes a byte array into Base64 format.
114 | * No blanks or line breaks are inserted in the output.
115 | *
116 | * @param in An array containing the data bytes to be encoded.
117 | * @param iLen Number of bytes to process in in.
118 | * @return A character array containing the Base64 encoded data.
119 | */
120 | public static char[] encode(byte[] in, int iLen) {
121 | return encode(in, 0, iLen);
122 | }
123 |
124 | /**
125 | * Encodes a byte array into Base64 format.
126 | * No blanks or line breaks are inserted in the output.
127 | *
128 | * @param in An array containing the data bytes to be encoded.
129 | * @param iOff Offset of the first byte in in to be processed.
130 | * @param iLen Number of bytes to process in in, starting at iOff.
131 | * @return A character array containing the Base64 encoded data.
132 | */
133 | public static char[] encode(byte[] in, int iOff, int iLen) {
134 | int oDataLen = (iLen * 4 + 2) / 3; // output length without padding
135 | int oLen = ((iLen + 2) / 3) * 4; // output length including padding
136 | char[] out = new char[oLen];
137 | int ip = iOff;
138 | int iEnd = iOff + iLen;
139 | int op = 0;
140 | while(ip < iEnd) {
141 | int i0 = in[ip++] & 0xff;
142 | int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
143 | int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
144 | int o0 = i0 >>> 2;
145 | int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
146 | int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
147 | int o3 = i2 & 0x3F;
148 | out[op++] = map1[o0];
149 | out[op++] = map1[o1];
150 | out[op] = op < oDataLen ? map1[o2] : '=';
151 | op++;
152 | out[op] = op < oDataLen ? map1[o3] : '=';
153 | op++;
154 | }
155 | return out;
156 | }
157 |
158 | /**
159 | * Decodes a string from Base64 format.
160 | * No blanks or line breaks are allowed within the Base64 encoded input data.
161 | *
162 | * @param s A Base64 String to be decoded.
163 | * @return A String containing the decoded data.
164 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
165 | */
166 | public static String decodeString(String s) {
167 | return new String(decode(s));
168 | }
169 |
170 | /**
171 | * Decodes a byte array from Base64 format and ignores line separators, tabs and blanks.
172 | * CR, LF, Tab and Space characters are ignored in the input data.
173 | * This method is compatible with sun.misc.BASE64Decoder.decodeBuffer(String).
174 | *
175 | * @param s A Base64 String to be decoded.
176 | * @return An array containing the decoded data bytes.
177 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
178 | */
179 | public static byte[] decodeLines(String s) {
180 | char[] buf = new char[s.length()];
181 | int p = 0;
182 | for(int ip = 0; ip < s.length(); ip++) {
183 | char c = s.charAt(ip);
184 | if(c != ' ' && c != '\r' && c != '\n' && c != '\t') buf[p++] = c;
185 | }
186 | return decode(buf, 0, p);
187 | }
188 |
189 | /**
190 | * Decodes a byte array from Base64 format.
191 | * No blanks or line breaks are allowed within the Base64 encoded input data.
192 | *
193 | * @param s A Base64 String to be decoded.
194 | * @return An array containing the decoded data bytes.
195 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
196 | */
197 | public static byte[] decode(String s) {
198 | return decode(s.toCharArray());
199 | }
200 |
201 | /**
202 | * Decodes a byte array from Base64 format.
203 | * No blanks or line breaks are allowed within the Base64 encoded input data.
204 | *
205 | * @param in A character array containing the Base64 encoded data.
206 | * @return An array containing the decoded data bytes.
207 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
208 | */
209 | public static byte[] decode(char[] in) {
210 | return decode(in, 0, in.length);
211 | }
212 |
213 | /**
214 | * Decodes a byte array from Base64 format.
215 | * No blanks or line breaks are allowed within the Base64 encoded input data.
216 | *
217 | * @param in A character array containing the Base64 encoded data.
218 | * @param iOff Offset of the first character in in to be processed.
219 | * @param iLen Number of characters to process in in, starting at iOff.
220 | * @return An array containing the decoded data bytes.
221 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
222 | */
223 | public static byte[] decode(char[] in, int iOff, int iLen) {
224 | if(iLen % 4 != 0) throw new IllegalArgumentException("Length of Base64 encoded input string is not a multiple of 4.");
225 | while(iLen > 0 && in[iOff + iLen - 1] == '=') iLen--;
226 | int oLen = (iLen * 3) / 4;
227 | byte[] out = new byte[oLen];
228 | int ip = iOff;
229 | int iEnd = iOff + iLen;
230 | int op = 0;
231 | while(ip < iEnd) {
232 | int i0 = in[ip++];
233 | int i1 = in[ip++];
234 | int i2 = ip < iEnd ? in[ip++] : 'A';
235 | int i3 = ip < iEnd ? in[ip++] : 'A';
236 | if(i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
237 | throw new IllegalArgumentException("Illegal character in Base64 encoded data.");
238 | int b0 = map2[i0];
239 | int b1 = map2[i1];
240 | int b2 = map2[i2];
241 | int b3 = map2[i3];
242 | if(b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
243 | throw new IllegalArgumentException("Illegal character in Base64 encoded data.");
244 | int o0 = (b0 << 2) | (b1 >>> 4);
245 | int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2);
246 | int o2 = ((b2 & 3) << 6) | b3;
247 | out[op++] = (byte) o0;
248 | if(op < oLen) out[op++] = (byte) o1;
249 | if(op < oLen) out[op++] = (byte) o2;
250 | }
251 | return out;
252 | }
253 |
254 | // Dummy constructor.
255 |
256 | private Base64Coder() {
257 | }
258 |
259 | } // end class Base64Coder
--------------------------------------------------------------------------------
/src/main/java/com/tinkerpop/blueprints/pgm/impls/blueredis/utils/Base64Coder.java:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * Copyright (c) 2010-2011. Dmitrii Dimandt *
3 | * *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); *
5 | * you may not use this file except in compliance with the License. *
6 | * You may obtain a copy of the License at *
7 | * *
8 | * http://www.apache.org/licenses/LICENSE-2.0 *
9 | * *
10 | * Unless required by applicable law or agreed to in writing, software *
11 | * distributed under the License is distributed on an "AS IS" BASIS, *
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13 | * See the License for the specific language governing permissions and *
14 | * limitations under the License. *
15 | ******************************************************************************/
16 |
17 | package com.tinkerpop.blueprints.pgm.impls.blueredis.utils;
18 |
19 | /**
20 | * A Base64 encoder/decoder.
21 | *
22 | *
23 | * This class is used to encode and decode data in Base64 format as described in RFC 1521.
24 | *
25 | *
26 | * Project home page: www.source-code.biz/base64coder/java
27 | * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
28 | * Multi-licensed: EPL / LGPL / AL / BSD.
29 | */
30 | public class Base64Coder {
31 |
32 | // The line separator string of the operating system.
33 | private static final String systemLineSeparator = System.getProperty("line.separator");
34 |
35 | // Mapping table from 6-bit nibbles to Base64 characters.
36 | private static char[] map1 = new char[64];
37 |
38 | static {
39 | int i = 0;
40 | for(char c = 'A'; c <= 'Z'; c++) map1[i++] = c;
41 | for(char c = 'a'; c <= 'z'; c++) map1[i++] = c;
42 | for(char c = '0'; c <= '9'; c++) map1[i++] = c;
43 | map1[i++] = '+';
44 | map1[i++] = '/';
45 | }
46 |
47 | // Mapping table from Base64 characters to 6-bit nibbles.
48 | private static byte[] map2 = new byte[128];
49 |
50 | static {
51 | for(int i = 0; i < map2.length; i++) map2[i] = -1;
52 | for(int i = 0; i < 64; i++) map2[map1[i]] = (byte) i;
53 | }
54 |
55 | /**
56 | * Encodes a string into Base64 format.
57 | * No blanks or line breaks are inserted.
58 | *
59 | * @param s A String to be encoded.
60 | * @return A String containing the Base64 encoded data.
61 | */
62 | public static String encodeString(String s) {
63 | return new String(encode(s.getBytes()));
64 | }
65 |
66 | /**
67 | * Encodes a byte array into Base 64 format and breaks the output into lines of 76 characters.
68 | * This method is compatible with sun.misc.BASE64Encoder.encodeBuffer(byte[]).
69 | *
70 | * @param in An array containing the data bytes to be encoded.
71 | * @return A String containing the Base64 encoded data, broken into lines.
72 | */
73 | public static String encodeLines(byte[] in) {
74 | return encodeLines(in, 0, in.length, 76, systemLineSeparator);
75 | }
76 |
77 | /**
78 | * Encodes a byte array into Base 64 format and breaks the output into lines.
79 | *
80 | * @param in An array containing the data bytes to be encoded.
81 | * @param iOff Offset of the first byte in in to be processed.
82 | * @param iLen Number of bytes to be processed in in, starting at iOff.
83 | * @param lineLen Line length for the output data. Should be a multiple of 4.
84 | * @param lineSeparator The line separator to be used to separate the output lines.
85 | * @return A String containing the Base64 encoded data, broken into lines.
86 | */
87 | public static String encodeLines(byte[] in, int iOff, int iLen, int lineLen, String lineSeparator) {
88 | int blockLen = (lineLen * 3) / 4;
89 | if(blockLen <= 0) throw new IllegalArgumentException();
90 | int lines = (iLen + blockLen - 1) / blockLen;
91 | int bufLen = ((iLen + 2) / 3) * 4 + lines * lineSeparator.length();
92 | StringBuilder buf = new StringBuilder(bufLen);
93 | int ip = 0;
94 | while(ip < iLen) {
95 | int l = Math.min(iLen - ip, blockLen);
96 | buf.append(encode(in, iOff + ip, l));
97 | buf.append(lineSeparator);
98 | ip += l;
99 | }
100 | return buf.toString();
101 | }
102 |
103 | /**
104 | * Encodes a byte array into Base64 format.
105 | * No blanks or line breaks are inserted in the output.
106 | *
107 | * @param in An array containing the data bytes to be encoded.
108 | * @return A character array containing the Base64 encoded data.
109 | */
110 | public static char[] encode(byte[] in) {
111 | return encode(in, 0, in.length);
112 | }
113 |
114 | /**
115 | * Encodes a byte array into Base64 format.
116 | * No blanks or line breaks are inserted in the output.
117 | *
118 | * @param in An array containing the data bytes to be encoded.
119 | * @param iLen Number of bytes to process in in.
120 | * @return A character array containing the Base64 encoded data.
121 | */
122 | public static char[] encode(byte[] in, int iLen) {
123 | return encode(in, 0, iLen);
124 | }
125 |
126 | /**
127 | * Encodes a byte array into Base64 format.
128 | * No blanks or line breaks are inserted in the output.
129 | *
130 | * @param in An array containing the data bytes to be encoded.
131 | * @param iOff Offset of the first byte in in to be processed.
132 | * @param iLen Number of bytes to process in in, starting at iOff.
133 | * @return A character array containing the Base64 encoded data.
134 | */
135 | public static char[] encode(byte[] in, int iOff, int iLen) {
136 | int oDataLen = (iLen * 4 + 2) / 3; // output length without padding
137 | int oLen = ((iLen + 2) / 3) * 4; // output length including padding
138 | char[] out = new char[oLen];
139 | int ip = iOff;
140 | int iEnd = iOff + iLen;
141 | int op = 0;
142 | while(ip < iEnd) {
143 | int i0 = in[ip++] & 0xff;
144 | int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
145 | int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
146 | int o0 = i0 >>> 2;
147 | int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
148 | int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
149 | int o3 = i2 & 0x3F;
150 | out[op++] = map1[o0];
151 | out[op++] = map1[o1];
152 | out[op] = op < oDataLen ? map1[o2] : '=';
153 | op++;
154 | out[op] = op < oDataLen ? map1[o3] : '=';
155 | op++;
156 | }
157 | return out;
158 | }
159 |
160 | /**
161 | * Decodes a string from Base64 format.
162 | * No blanks or line breaks are allowed within the Base64 encoded input data.
163 | *
164 | * @param s A Base64 String to be decoded.
165 | * @return A String containing the decoded data.
166 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
167 | */
168 | public static String decodeString(String s) {
169 | return new String(decode(s));
170 | }
171 |
172 | /**
173 | * Decodes a byte array from Base64 format and ignores line separators, tabs and blanks.
174 | * CR, LF, Tab and Space characters are ignored in the input data.
175 | * This method is compatible with sun.misc.BASE64Decoder.decodeBuffer(String).
176 | *
177 | * @param s A Base64 String to be decoded.
178 | * @return An array containing the decoded data bytes.
179 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
180 | */
181 | public static byte[] decodeLines(String s) {
182 | char[] buf = new char[s.length()];
183 | int p = 0;
184 | for(int ip = 0; ip < s.length(); ip++) {
185 | char c = s.charAt(ip);
186 | if(c != ' ' && c != '\r' && c != '\n' && c != '\t') buf[p++] = c;
187 | }
188 | return decode(buf, 0, p);
189 | }
190 |
191 | /**
192 | * Decodes a byte array from Base64 format.
193 | * No blanks or line breaks are allowed within the Base64 encoded input data.
194 | *
195 | * @param s A Base64 String to be decoded.
196 | * @return An array containing the decoded data bytes.
197 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
198 | */
199 | public static byte[] decode(String s) {
200 | return decode(s.toCharArray());
201 | }
202 |
203 | /**
204 | * Decodes a byte array from Base64 format.
205 | * No blanks or line breaks are allowed within the Base64 encoded input data.
206 | *
207 | * @param in A character array containing the Base64 encoded data.
208 | * @return An array containing the decoded data bytes.
209 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
210 | */
211 | public static byte[] decode(char[] in) {
212 | return decode(in, 0, in.length);
213 | }
214 |
215 | /**
216 | * Decodes a byte array from Base64 format.
217 | * No blanks or line breaks are allowed within the Base64 encoded input data.
218 | *
219 | * @param in A character array containing the Base64 encoded data.
220 | * @param iOff Offset of the first character in in to be processed.
221 | * @param iLen Number of characters to process in in, starting at iOff.
222 | * @return An array containing the decoded data bytes.
223 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
224 | */
225 | public static byte[] decode(char[] in, int iOff, int iLen) {
226 | if(iLen % 4 != 0) throw new IllegalArgumentException("Length of Base64 encoded input string is not a multiple of 4.");
227 | while(iLen > 0 && in[iOff + iLen - 1] == '=') iLen--;
228 | int oLen = (iLen * 3) / 4;
229 | byte[] out = new byte[oLen];
230 | int ip = iOff;
231 | int iEnd = iOff + iLen;
232 | int op = 0;
233 | while(ip < iEnd) {
234 | int i0 = in[ip++];
235 | int i1 = in[ip++];
236 | int i2 = ip < iEnd ? in[ip++] : 'A';
237 | int i3 = ip < iEnd ? in[ip++] : 'A';
238 | if(i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
239 | throw new IllegalArgumentException("Illegal character in Base64 encoded data.");
240 | int b0 = map2[i0];
241 | int b1 = map2[i1];
242 | int b2 = map2[i2];
243 | int b3 = map2[i3];
244 | if(b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
245 | throw new IllegalArgumentException("Illegal character in Base64 encoded data.");
246 | int o0 = (b0 << 2) | (b1 >>> 4);
247 | int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2);
248 | int o2 = ((b2 & 3) << 6) | b3;
249 | out[op++] = (byte) o0;
250 | if(op < oLen) out[op++] = (byte) o1;
251 | if(op < oLen) out[op++] = (byte) o2;
252 | }
253 | return out;
254 | }
255 |
256 | // Dummy constructor.
257 |
258 | private Base64Coder() {
259 | }
260 |
261 | } // end class Base64Coder
--------------------------------------------------------------------------------