View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.commons.rdf.simple;
19  
20  import java.nio.charset.StandardCharsets;
21  import java.util.Objects;
22  import java.util.UUID;
23  import java.util.concurrent.atomic.AtomicLong;
24  
25  import org.apache.commons.rdf.api.BlankNode;
26  import org.apache.commons.rdf.simple.SimpleRDF.SimpleRDFTerm;
27  
28  /**
29   * A simple implementation of BlankNode.
30   */
31  final class BlankNodeImpl implements BlankNode, SimpleRDFTerm {
32  
33      private static final UUID SALT = UUID.randomUUID();
34      private static final AtomicLong COUNTER = new AtomicLong();
35  
36      private final String uniqueReference;
37  
38      public BlankNodeImpl() {
39          this(SALT, Long.toString(COUNTER.incrementAndGet()));
40      }
41  
42      public BlankNodeImpl(final UUID uuidSalt, final String name) {
43          if (Objects.requireNonNull(name).isEmpty()) {
44              throw new IllegalArgumentException("Invalid blank node id: " + name);
45          }
46  
47          // Build a semi-URN - to be hashed for a name-based UUID below
48          // Both the scope and the id are used to create the UUID, ensuring that
49          // a caller can reliably create the same bnode if necessary by sending
50          // in the same scope to RDF.createBlankNode(String)
51          final String uuidInput = "urn:uuid:" + uuidSalt + "#" + name;
52  
53          // The above is not a good value for this.id, as the id
54          // needs to be further escaped for
55          // ntriplesString() (there are no restrictions on
56          // RDF.createBlankNode(String) ).
57  
58          // Rather than implement ntriples escaping here, and knowing
59          // our uniqueReference() contain a UUID anyway, we simply
60          // create another name-based UUID, and use it within both
61          // uniqueReference() and within ntriplesString().
62          //
63          // A side-effect from this is that the blank node identifier
64          // is not preserved or shown in ntriplesString. In a way
65          // this is a feature, not a bug. as the contract for RDF
66          // has no such requirement.
67          this.uniqueReference = UUID.nameUUIDFromBytes(uuidInput.getBytes(StandardCharsets.UTF_8)).toString();
68      }
69  
70      @Override
71      public String uniqueReference() {
72          return uniqueReference;
73      }
74  
75      @Override
76      public String ntriplesString() {
77          return "_:" + uniqueReference;
78      }
79  
80      @Override
81      public String toString() {
82          return ntriplesString();
83      }
84  
85      @Override
86      public int hashCode() {
87          return uniqueReference.hashCode();
88      }
89  
90      @Override
91      public boolean equals(final Object obj) {
92          if (this == obj) {
93              return true;
94          }
95          if (obj == null) {
96              return false;
97          }
98          // We don't support equality with other implementations
99          if (!(obj instanceof BlankNodeImpl)) {
100             return false;
101         }
102         final BlankNodeImpl other = (BlankNodeImpl) obj;
103         if (uniqueReference == null) {
104             if (other.uniqueReference != null) {
105                 return false;
106             }
107         } else if (!uniqueReference.equals(other.uniqueReference)) {
108             return false;
109         }
110         return true;
111     }
112 
113 }