1 package eu.fbk.knowledgestore.data;
2
3 import com.google.common.base.*;
4 import com.google.common.base.Objects;
5 import com.google.common.collect.*;
6 import com.google.common.hash.Hasher;
7 import com.google.common.hash.Hashing;
8 import com.google.common.io.Resources;
9 import com.google.common.primitives.*;
10 import com.google.common.util.concurrent.ListeningScheduledExecutorService;
11 import eu.fbk.knowledgestore.internal.Util;
12 import eu.fbk.knowledgestore.internal.rdf.CompactValueFactory;
13 import eu.fbk.rdfpro.util.Namespaces;
14 import org.openrdf.model.*;
15 import org.openrdf.model.impl.ValueFactoryImpl;
16 import org.openrdf.model.vocabulary.XMLSchema;
17
18 import javax.annotation.Nullable;
19 import javax.xml.datatype.DatatypeConstants;
20 import javax.xml.datatype.DatatypeFactory;
21 import javax.xml.datatype.XMLGregorianCalendar;
22 import java.io.File;
23 import java.lang.reflect.Array;
24 import java.math.BigDecimal;
25 import java.math.BigInteger;
26 import java.net.URL;
27 import java.nio.charset.Charset;
28 import java.util.*;
29 import java.util.concurrent.ScheduledExecutorService;
30 import java.util.concurrent.atomic.AtomicBoolean;
31 import java.util.concurrent.atomic.AtomicInteger;
32 import java.util.concurrent.atomic.AtomicLong;
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102 public final class Data {
103
104 private static final Ordering<Object> TOTAL_ORDERING = new TotalOrdering();
105
106 private static final Comparator<Object> PARTIAL_ORDERING = new PartialOrdering(TOTAL_ORDERING);
107
108 private static final Map<String, String> COMMON_NAMESPACES = Namespaces.DEFAULT.uriMap();
109
110 private static final Map<String, String> COMMON_PREFIXES = Namespaces.DEFAULT.prefixMap();
111
112 private static final Set<String> UNCOMPRESSIBLE_MIME_TYPES;
113
114 private static final Map<String, String> EXTENSIONS_TO_MIME_TYPES;
115
116 private static final Map<String, List<String>> MIME_TYPES_TO_EXTENSIONS;
117
118 private static final Map<String, URI> LANGUAGE_CODES_TO_URIS;
119
120 private static final Map<URI, String> LANGUAGE_URIS_TO_CODES;
121
122 private static final DatatypeFactory DATATYPE_FACTORY;
123
124 private static final ValueFactory VALUE_FACTORY;
125
126 private static ListeningScheduledExecutorService executor;
127
128 private static AtomicBoolean executorPrivate = new AtomicBoolean();
129
130 static {
131 VALUE_FACTORY = CompactValueFactory.getInstance();
132 try {
133 DATATYPE_FACTORY = DatatypeFactory.newInstance();
134 } catch (final Throwable ex) {
135 throw new Error("Unexpected exception (!): " + ex.getMessage(), ex);
136 }
137 final Map<String, URI> codesToURIs = Maps.newHashMap();
138 final Map<URI, String> urisToCodes = Maps.newHashMap();
139 for (final String language : Locale.getISOLanguages()) {
140 final Locale locale = new Locale(language);
141 final URI uri = Data.getValueFactory().createURI("http://lexvo.org/id/iso639-3/",
142 locale.getISO3Language());
143 codesToURIs.put(language, uri);
144 urisToCodes.put(uri, language);
145 }
146 LANGUAGE_CODES_TO_URIS = ImmutableMap.copyOf(codesToURIs);
147 LANGUAGE_URIS_TO_CODES = ImmutableMap.copyOf(urisToCodes);
148 }
149
150 static {
151 try {
152 final ImmutableSet.Builder<String> uncompressibleMtsBuilder = ImmutableSet.builder();
153 final ImmutableMap.Builder<String, String> extToMtIndexBuilder = ImmutableMap
154 .builder();
155 final ImmutableMap.Builder<String, List<String>> mtToExtsIndexBuilder = ImmutableMap
156 .builder();
157
158 final URL resource = Data.class.getResource("mimetypes");
159 for (final String line : Resources.readLines(resource, Charsets.UTF_8)) {
160 if (!line.isEmpty() && line.charAt(0) != '#') {
161 final Iterator<String> iterator = Splitter.on(' ').trimResults()
162 .omitEmptyStrings().split(line).iterator();
163 final String mimeType = iterator.next();
164 final ImmutableList.Builder<String> extBuilder = ImmutableList.builder();
165 while (iterator.hasNext()) {
166 final String token = iterator.next();
167 if ("*".equals(token)) {
168 uncompressibleMtsBuilder.add(mimeType);
169 } else {
170 extBuilder.add(token);
171 extToMtIndexBuilder.put(token, mimeType);
172 }
173 }
174 mtToExtsIndexBuilder.put(mimeType, extBuilder.build());
175 }
176 }
177
178 UNCOMPRESSIBLE_MIME_TYPES = uncompressibleMtsBuilder.build();
179 EXTENSIONS_TO_MIME_TYPES = extToMtIndexBuilder.build();
180 MIME_TYPES_TO_EXTENSIONS = mtToExtsIndexBuilder.build();
181
182 } catch (final Throwable ex) {
183 throw new Error("Unexpected exception (!): " + ex.getMessage(), ex);
184 }
185 }
186
187
188
189
190
191
192
193
194
195 public static ListeningScheduledExecutorService getExecutor() {
196 synchronized (executorPrivate) {
197 if (executor == null) {
198 final String threadName = MoreObjects.firstNonNull(
199 System.getProperty("eu.fbk.knowledgestore.threadName"), "worker-%02d");
200 int threadCount = 32;
201 try {
202 threadCount = Integer.parseInt(System
203 .getProperty("eu.fbk.knowledgestore.threadCount"));
204 } catch (final Throwable ex) {
205
206 }
207 executor = Util.newScheduler(threadCount, threadName, true);
208 executorPrivate.set(true);
209 }
210 return executor;
211 }
212 }
213
214
215
216
217
218
219
220
221
222 public static void setExecutor(final ScheduledExecutorService newExecutor) {
223 Preconditions.checkNotNull(newExecutor);
224 ScheduledExecutorService executorToShutdown = null;
225 synchronized (executorPrivate) {
226 if (executor != null && executorPrivate.get()) {
227 executorToShutdown = executor;
228 }
229 executor = Util.decorate(newExecutor);
230 executorPrivate.set(false);
231 }
232 if (executorToShutdown != null) {
233 executorToShutdown.shutdown();
234 }
235 }
236
237
238
239
240
241
242
243
244
245
246 public static ValueFactory getValueFactory() {
247 return VALUE_FACTORY;
248 }
249
250
251
252
253
254
255
256 public static DatatypeFactory getDatatypeFactory() {
257 return DATATYPE_FACTORY;
258 }
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274 public static Comparator<Object> getTotalComparator() {
275 return TOTAL_ORDERING;
276 }
277
278
279
280
281
282
283
284
285
286
287 public static Comparator<Object> getPartialComparator() {
288 return PARTIAL_ORDERING;
289 }
290
291
292
293
294
295
296
297
298 public static Map<String, String> getNamespaceMap() {
299 return COMMON_NAMESPACES;
300 }
301
302
303
304
305
306
307
308 public static Map<String, String> newNamespaceMap() {
309 return new NamespaceMap();
310 }
311
312
313
314
315
316
317
318
319
320
321
322
323
324 public static Map<String, String> newNamespaceMap(
325 final Map<String, String> primaryNamespaceMap,
326 final Map<String, String> secondaryNamespaceMap) {
327
328 Preconditions.checkNotNull(primaryNamespaceMap);
329 Preconditions.checkNotNull(secondaryNamespaceMap);
330
331 if (primaryNamespaceMap == secondaryNamespaceMap) {
332 return primaryNamespaceMap;
333 } else {
334 return new NamespaceCombinedMap(primaryNamespaceMap, secondaryNamespaceMap);
335 }
336 }
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353 @Nullable
354 public static String namespaceToPrefix(final String namespace,
355 final Map<String, String> namespaceMap) {
356
357 Preconditions.checkNotNull(namespace);
358
359 if (namespaceMap == COMMON_NAMESPACES) {
360 return COMMON_PREFIXES.get(namespace);
361
362 } else if (namespaceMap instanceof NamespaceCombinedMap) {
363 final NamespaceCombinedMap map = (NamespaceCombinedMap) namespaceMap;
364 String prefix = namespaceToPrefix(namespace, map.primaryNamespaces);
365 if (prefix == null) {
366 prefix = namespaceToPrefix(namespace, map.secondaryNamespaces);
367 }
368 return prefix;
369
370 } else if (namespaceMap instanceof NamespaceMap) {
371 return ((NamespaceMap) namespaceMap).getPrefix(namespace);
372
373 } else if (namespaceMap instanceof BiMap) {
374 return ((BiMap<String, String>) namespaceMap).inverse().get(namespace);
375
376 } else {
377 Preconditions.checkNotNull(namespaceMap);
378 for (final Map.Entry<String, String> entry : namespaceMap.entrySet()) {
379 if (entry.getValue().equals(namespace)) {
380 return entry.getKey();
381 }
382 }
383 return null;
384 }
385 }
386
387
388
389
390
391
392
393
394
395
396
397 public static boolean isMimeTypeCompressible(final String mimeType) {
398 Preconditions.checkNotNull(mimeType);
399 final int index = mimeType.indexOf(';');
400 final String key = (index < 0 ? mimeType : mimeType.substring(0, index)).toLowerCase();
401 return !UNCOMPRESSIBLE_MIME_TYPES.contains(key);
402 }
403
404
405
406
407
408
409
410
411
412
413 public static String extensionToMimeType(final String fileNameOrExtension) {
414 Preconditions.checkNotNull(fileNameOrExtension);
415 final int index = fileNameOrExtension.lastIndexOf('.');
416 final String extension = index < 0 ? fileNameOrExtension : fileNameOrExtension
417 .substring(index + 1);
418 return EXTENSIONS_TO_MIME_TYPES.get(extension);
419 }
420
421
422
423
424
425
426
427
428
429
430
431 public static List<String> mimeTypeToExtensions(final String mimeType) {
432 Preconditions.checkNotNull(mimeType);
433 final int index = mimeType.indexOf(';');
434 final String key = (index < 0 ? mimeType : mimeType.substring(0, index)).toLowerCase();
435 final List<String> result = MIME_TYPES_TO_EXTENSIONS.get(key);
436 return result != null ? result : ImmutableList.<String>of();
437 }
438
439
440
441
442
443
444
445
446
447
448
449 @Nullable
450 public static URI languageCodeToURI(@Nullable final String code)
451 throws IllegalArgumentException {
452 if (code == null) {
453 return null;
454 }
455 final int length = code.length();
456 if (length == 2) {
457 final URI uri = LANGUAGE_CODES_TO_URIS.get(code);
458 if (uri != null) {
459 return uri;
460 }
461 } else if (length == 3) {
462 final URI uri = Data.getValueFactory().createURI(
463 "http://lexvo.org/id/iso639-3/" + code);
464 if (LANGUAGE_URIS_TO_CODES.containsKey(uri)) {
465 return uri;
466 }
467 }
468 throw new IllegalArgumentException("Invalid language code: " + code);
469 }
470
471
472
473
474
475
476
477
478
479
480
481 @Nullable
482 public static String languageURIToCode(@Nullable final URI uri)
483 throws IllegalArgumentException {
484 if (uri == null) {
485 return null;
486 }
487 final String code = LANGUAGE_URIS_TO_CODES.get(uri);
488 if (code != null) {
489 return code;
490 }
491 throw new IllegalArgumentException("Invalid language URI: " + uri);
492 }
493
494
495
496
497
498
499
500
501
502
503 public static String hash(final Object... objects) {
504 final Hasher hasher = Hashing.md5().newHasher();
505 for (final Object object : objects) {
506 if (object instanceof CharSequence) {
507 hasher.putString((CharSequence) object, Charsets.UTF_16LE);
508 } else if (object instanceof byte[]) {
509 hasher.putBytes((byte[]) object);
510 } else if (object instanceof Character) {
511 hasher.putChar((Character) object);
512 } else if (object instanceof Boolean) {
513 hasher.putBoolean((Boolean) object);
514 } else if (object instanceof Integer) {
515 hasher.putInt(((Integer) object).intValue());
516 } else if (object instanceof Long) {
517 hasher.putLong(((Long) object).longValue());
518 } else if (object instanceof Double) {
519 hasher.putDouble(((Double) object).doubleValue());
520 } else if (object instanceof Float) {
521 hasher.putFloat(((Float) object).floatValue());
522 } else if (object instanceof Byte) {
523 hasher.putByte(((Byte) object).byteValue());
524 } else {
525 hasher.putString(object.toString(), Charsets.UTF_16LE);
526 }
527 }
528 final byte[] bytes = hasher.hash().asBytes();
529 final StringBuilder builder = new StringBuilder(16);
530 int max = 52;
531 for (int i = 0; i < bytes.length; ++i) {
532 final int n = (bytes[i] & 0x7F) % max;
533 if (n < 26) {
534 builder.append((char) (65 + n));
535 } else if (n < 52) {
536 builder.append((char) (71 + n));
537 } else {
538 builder.append((char) (n - 4));
539 }
540 max = 62;
541 }
542 return builder.toString();
543 }
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612 @SuppressWarnings("unchecked")
613 @Nullable
614 public static <T> T convert(@Nullable final Object object, final Class<T> clazz)
615 throws IllegalArgumentException {
616 if (object == null) {
617 Preconditions.checkNotNull(clazz);
618 return null;
619 }
620 if (clazz.isInstance(object)) {
621 return (T) object;
622 }
623 final T result = (T) convertObject(object, clazz);
624 if (result != null) {
625 return result;
626 }
627 throw new IllegalArgumentException("Unsupported conversion of " + object + " to " + clazz);
628 }
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646 @SuppressWarnings("unchecked")
647 @Nullable
648 public static <T> T convert(@Nullable final Object object, final Class<T> clazz,
649 @Nullable final T defaultValue) {
650 if (object == null) {
651 Preconditions.checkNotNull(clazz);
652 return defaultValue;
653 }
654 if (clazz.isInstance(object)) {
655 return (T) object;
656 }
657 try {
658 final T result = (T) convertObject(object, clazz);
659 return result != null ? result : defaultValue;
660 } catch (final RuntimeException ex) {
661 return defaultValue;
662 }
663 }
664
665 @Nullable
666 private static Object convertObject(final Object object, final Class<?> clazz) {
667 if (object instanceof Literal) {
668 return convertLiteral((Literal) object, clazz);
669 } else if (object instanceof URI) {
670 return convertURI((URI) object, clazz);
671 } else if (object instanceof String) {
672 return convertString((String) object, clazz);
673 } else if (object instanceof Number) {
674 return convertNumber((Number) object, clazz);
675 } else if (object instanceof Boolean) {
676 return convertBoolean((Boolean) object, clazz);
677 } else if (object instanceof XMLGregorianCalendar) {
678 return convertCalendar((XMLGregorianCalendar) object, clazz);
679 } else if (object instanceof BNode) {
680 return convertBNode((BNode) object, clazz);
681 } else if (object instanceof Statement) {
682 return convertStatement((Statement) object, clazz);
683 } else if (object instanceof Record) {
684 return convertRecord((Record) object, clazz);
685 } else if (object instanceof GregorianCalendar) {
686 final XMLGregorianCalendar calendar = getDatatypeFactory().newXMLGregorianCalendar(
687 (GregorianCalendar) object);
688 return clazz == XMLGregorianCalendar.class ? calendar : convertCalendar(calendar,
689 clazz);
690 } else if (object instanceof Date) {
691 final GregorianCalendar calendar = new GregorianCalendar();
692 calendar.setTime((Date) object);
693 final XMLGregorianCalendar xmlCalendar = getDatatypeFactory().newXMLGregorianCalendar(
694 calendar);
695 return clazz == XMLGregorianCalendar.class ? xmlCalendar : convertCalendar(
696 xmlCalendar, clazz);
697 } else if (object instanceof Enum<?>) {
698 return convertEnum((Enum<?>) object, clazz);
699 } else if (object instanceof File) {
700 return convertFile((File) object, clazz);
701 }
702 return null;
703 }
704
705 @Nullable
706 private static Object convertStatement(final Statement statement, final Class<?> clazz) {
707 if (clazz.isAssignableFrom(String.class)) {
708 return statement.toString();
709 }
710 return null;
711 }
712
713 @Nullable
714 private static Object convertLiteral(final Literal literal, final Class<?> clazz) {
715 final URI datatype = literal.getDatatype();
716 if (datatype == null || datatype.equals(XMLSchema.STRING)) {
717 return convertString(literal.getLabel(), clazz);
718 } else if (datatype.equals(XMLSchema.BOOLEAN)) {
719 return convertBoolean(literal.booleanValue(), clazz);
720 } else if (datatype.equals(XMLSchema.DATE) || datatype.equals(XMLSchema.DATETIME)) {
721 return convertCalendar(literal.calendarValue(), clazz);
722 } else if (datatype.equals(XMLSchema.INT)) {
723 return convertNumber(literal.intValue(), clazz);
724 } else if (datatype.equals(XMLSchema.LONG)) {
725 return convertNumber(literal.longValue(), clazz);
726 } else if (datatype.equals(XMLSchema.DOUBLE)) {
727 return convertNumber(literal.doubleValue(), clazz);
728 } else if (datatype.equals(XMLSchema.FLOAT)) {
729 return convertNumber(literal.floatValue(), clazz);
730 } else if (datatype.equals(XMLSchema.SHORT)) {
731 return convertNumber(literal.shortValue(), clazz);
732 } else if (datatype.equals(XMLSchema.BYTE)) {
733 return convertNumber(literal.byteValue(), clazz);
734 } else if (datatype.equals(XMLSchema.DECIMAL)) {
735 return convertNumber(literal.decimalValue(), clazz);
736 } else if (datatype.equals(XMLSchema.INTEGER)) {
737 return convertNumber(literal.integerValue(), clazz);
738 } else if (datatype.equals(XMLSchema.NON_NEGATIVE_INTEGER)
739 || datatype.equals(XMLSchema.NON_POSITIVE_INTEGER)
740 || datatype.equals(XMLSchema.NEGATIVE_INTEGER)
741 || datatype.equals(XMLSchema.POSITIVE_INTEGER)) {
742 return convertNumber(literal.integerValue(), clazz);
743 } else if (datatype.equals(XMLSchema.NORMALIZEDSTRING) || datatype.equals(XMLSchema.TOKEN)
744 || datatype.equals(XMLSchema.NMTOKEN) || datatype.equals(XMLSchema.LANGUAGE)
745 || datatype.equals(XMLSchema.NAME) || datatype.equals(XMLSchema.NCNAME)) {
746 return convertString(literal.getLabel(), clazz);
747 }
748 return null;
749 }
750
751 @Nullable
752 private static Object convertBoolean(final Boolean bool, final Class<?> clazz) {
753 if (clazz == Boolean.class || clazz == boolean.class) {
754 return bool;
755 } else if (clazz.isAssignableFrom(Literal.class)) {
756 return getValueFactory().createLiteral(bool);
757 } else if (clazz.isAssignableFrom(String.class)) {
758 return bool.toString();
759 }
760 return null;
761 }
762
763 @Nullable
764 private static Object convertString(final String string, final Class<?> clazz) {
765 if (clazz.isInstance(string)) {
766 return string;
767 } else if (clazz.isAssignableFrom(Literal.class)) {
768 return getValueFactory().createLiteral(string, XMLSchema.STRING);
769 } else if (clazz.isAssignableFrom(URI.class)) {
770 return getValueFactory().createURI(string);
771 } else if (clazz.isAssignableFrom(BNode.class)) {
772 return getValueFactory().createBNode(
773 string.startsWith("_:") ? string.substring(2) : string);
774 } else if (clazz == Boolean.class || clazz == boolean.class) {
775 return Boolean.valueOf(string);
776 } else if (clazz == Integer.class || clazz == int.class) {
777 return Integer.valueOf(string);
778 } else if (clazz == Long.class || clazz == long.class) {
779 return Long.valueOf(string);
780 } else if (clazz == Double.class || clazz == double.class) {
781 return Double.valueOf(string);
782 } else if (clazz == Float.class || clazz == float.class) {
783 return Float.valueOf(string);
784 } else if (clazz == Short.class || clazz == short.class) {
785 return Short.valueOf(string);
786 } else if (clazz == Byte.class || clazz == byte.class) {
787 return Byte.valueOf(string);
788 } else if (clazz == BigDecimal.class) {
789 return new BigDecimal(string);
790 } else if (clazz == BigInteger.class) {
791 return new BigInteger(string);
792 } else if (clazz == AtomicInteger.class) {
793 return new AtomicInteger(Integer.parseInt(string));
794 } else if (clazz == AtomicLong.class) {
795 return new AtomicLong(Long.parseLong(string));
796 } else if (clazz == Date.class) {
797 final String fixed = string.contains("T") ? string : string + "T00:00:00";
798 return getDatatypeFactory().newXMLGregorianCalendar(fixed).toGregorianCalendar()
799 .getTime();
800 } else if (clazz.isAssignableFrom(GregorianCalendar.class)) {
801 final String fixed = string.contains("T") ? string : string + "T00:00:00";
802 return getDatatypeFactory().newXMLGregorianCalendar(fixed).toGregorianCalendar();
803 } else if (clazz.isAssignableFrom(XMLGregorianCalendar.class)) {
804 final String fixed = string.contains("T") ? string : string + "T00:00:00";
805 return getDatatypeFactory().newXMLGregorianCalendar(fixed);
806 } else if (clazz == Character.class || clazz == char.class) {
807 return string.isEmpty() ? null : string.charAt(0);
808 } else if (clazz.isEnum()) {
809 for (final Object constant : clazz.getEnumConstants()) {
810 if (string.equalsIgnoreCase(((Enum<?>) constant).name())) {
811 return constant;
812 }
813 }
814 throw new IllegalArgumentException("Illegal " + clazz.getSimpleName() + " constant: "
815 + string);
816 } else if (clazz == File.class) {
817 return new File(string);
818 }
819 return null;
820 }
821
822 @Nullable
823 private static Object convertNumber(final Number number, final Class<?> clazz) {
824 if (clazz.isAssignableFrom(Literal.class)) {
825
826
827 if (number instanceof Integer || number instanceof AtomicInteger) {
828 return getValueFactory().createLiteral(number.intValue());
829 } else if (number instanceof Long || number instanceof AtomicLong) {
830 return getValueFactory().createLiteral(number.longValue());
831 } else if (number instanceof Double) {
832 return getValueFactory().createLiteral(number.doubleValue());
833 } else if (number instanceof Float) {
834 return getValueFactory().createLiteral(number.floatValue());
835 } else if (number instanceof Short) {
836 return getValueFactory().createLiteral(number.shortValue());
837 } else if (number instanceof Byte) {
838 return getValueFactory().createLiteral(number.byteValue());
839 } else if (number instanceof BigDecimal) {
840 return getValueFactory().createLiteral(number.toString(), XMLSchema.DECIMAL);
841 } else if (number instanceof BigInteger) {
842 return getValueFactory().createLiteral(number.toString(), XMLSchema.INTEGER);
843 }
844 } else if (clazz.isAssignableFrom(String.class)) {
845 return number.toString();
846 } else if (clazz == Integer.class || clazz == int.class) {
847 return Integer.valueOf(number.intValue());
848 } else if (clazz == Long.class || clazz == long.class) {
849 return Long.valueOf(number.longValue());
850 } else if (clazz == Double.class || clazz == double.class) {
851 return Double.valueOf(number.doubleValue());
852 } else if (clazz == Float.class || clazz == float.class) {
853 return Float.valueOf(number.floatValue());
854 } else if (clazz == Short.class || clazz == short.class) {
855 return Short.valueOf(number.shortValue());
856 } else if (clazz == Byte.class || clazz == byte.class) {
857 return Byte.valueOf(number.byteValue());
858 } else if (clazz == BigDecimal.class) {
859 return toBigDecimal(number);
860 } else if (clazz == BigInteger.class) {
861 return toBigInteger(number);
862 } else if (clazz == AtomicInteger.class) {
863 return new AtomicInteger(number.intValue());
864 } else if (clazz == AtomicLong.class) {
865 return new AtomicLong(number.longValue());
866 }
867 return null;
868 }
869
870 @Nullable
871 private static Object convertCalendar(final XMLGregorianCalendar calendar,
872 final Class<?> clazz) {
873 if (clazz.isInstance(calendar)) {
874 return calendar;
875 } else if (clazz.isAssignableFrom(Literal.class)) {
876 return getValueFactory().createLiteral(calendar);
877 } else if (clazz.isAssignableFrom(String.class)) {
878 return calendar.toXMLFormat();
879 } else if (clazz == Date.class) {
880 return calendar.toGregorianCalendar().getTime();
881 } else if (clazz.isAssignableFrom(GregorianCalendar.class)) {
882 return calendar.toGregorianCalendar();
883 }
884 return null;
885 }
886
887 @Nullable
888 private static Object convertURI(final URI uri, final Class<?> clazz) {
889 if (clazz.isInstance(uri)) {
890 return uri;
891 } else if (clazz.isAssignableFrom(String.class)) {
892 return uri.stringValue();
893 } else if (clazz == Record.class) {
894 return Record.create(uri);
895 }
896 return null;
897 }
898
899 @Nullable
900 private static Object convertBNode(final BNode bnode, final Class<?> clazz) {
901 if (clazz.isInstance(bnode)) {
902 return bnode;
903 } else if (clazz.isAssignableFrom(URI.class)) {
904 return getValueFactory().createURI("bnode:" + bnode.getID());
905 } else if (clazz.isAssignableFrom(String.class)) {
906 return "_:" + bnode.getID();
907 }
908 return null;
909 }
910
911 @Nullable
912 private static Object convertRecord(final Record record, final Class<?> clazz) {
913 if (clazz.isInstance(record)) {
914 return record;
915 } else if (clazz.isAssignableFrom(URI.class)) {
916 return record.getID();
917 } else if (clazz.isAssignableFrom(String.class)) {
918 return record.toString();
919 }
920 return null;
921 }
922
923 @Nullable
924 private static Object convertEnum(final Enum<?> constant, final Class<?> clazz) {
925 if (clazz.isInstance(constant)) {
926 return constant;
927 } else if (clazz.isAssignableFrom(String.class)) {
928 return constant.name();
929 } else if (clazz.isAssignableFrom(Literal.class)) {
930 return getValueFactory().createLiteral(constant.name(), XMLSchema.STRING);
931 }
932 return null;
933 }
934
935 @Nullable
936 private static Object convertFile(final File file, final Class<?> clazz) {
937 if (clazz.isInstance(file)) {
938 return clazz.cast(file);
939 } else if (clazz.isAssignableFrom(URI.class)) {
940 return VALUE_FACTORY.createURI("file://" + file.getAbsolutePath());
941 } else if (clazz.isAssignableFrom(String.class)) {
942 return file.getAbsolutePath();
943 }
944 return null;
945 }
946
947 private static BigDecimal toBigDecimal(final Number number) {
948 if (number instanceof BigDecimal) {
949 return (BigDecimal) number;
950 } else if (number instanceof BigInteger) {
951 return new BigDecimal((BigInteger) number);
952 } else if (number instanceof Double || number instanceof Float) {
953 final double value = number.doubleValue();
954 return Double.isInfinite(value) || Double.isNaN(value) ? null : new BigDecimal(value);
955 } else {
956 return new BigDecimal(number.longValue());
957 }
958 }
959
960 private static BigInteger toBigInteger(final Number number) {
961 if (number instanceof BigInteger) {
962 return (BigInteger) number;
963 } else if (number instanceof BigDecimal) {
964 return ((BigDecimal) number).toBigInteger();
965 } else if (number instanceof Double || number instanceof Float) {
966 return new BigDecimal(number.doubleValue()).toBigInteger();
967 } else {
968 return BigInteger.valueOf(number.longValue());
969 }
970 }
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991 @Nullable
992 public static Object normalize(@Nullable final Object object) throws IllegalArgumentException {
993 if (object == null || object instanceof Record || object instanceof Value
994 || object instanceof Statement) {
995 return object;
996 }
997 if (object.getClass().isArray()) {
998 final int length = Array.getLength(object);
999 if (length == 0) {
1000 return null;
1001 }
1002 if (length == 1) {
1003 return normalize(Array.get(object, 0));
1004 }
1005 throw new IllegalArgumentException(
1006 "Cannot extract a unique node from array of length " + length);
1007 }
1008 if (object instanceof Iterable<?>) {
1009 Object result = null;
1010 for (final Object element : (Iterable<?>) object) {
1011 if (result != null) {
1012 throw new IllegalArgumentException(
1013 "cannot extract a unique node from iterable " + object);
1014 }
1015 result = normalize(element);
1016 }
1017 return result;
1018 }
1019 return convert(object, Value.class);
1020 }
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042 public static boolean normalize(final Object object, final Collection<Object> collection)
1043 throws IllegalArgumentException {
1044 if (object == null) {
1045 return false;
1046 } else if (object.getClass().isArray()) {
1047 final int length = Array.getLength(object);
1048 if (length == 0) {
1049 return false;
1050 } else if (length == 1) {
1051 return normalize(Array.get(object, 0), collection);
1052 } else if (object instanceof Object[]) {
1053 return normalize(Arrays.asList((Object[]) object), collection);
1054 } else if (object instanceof int[]) {
1055 return normalize(Ints.asList((int[]) object), collection);
1056 } else if (object instanceof long[]) {
1057 return normalize(Longs.asList((long[]) object), collection);
1058 } else if (object instanceof double[]) {
1059 return normalize(Doubles.asList((double[]) object), collection);
1060 } else if (object instanceof float[]) {
1061 return normalize(Floats.asList((float[]) object), collection);
1062 } else if (object instanceof short[]) {
1063 return normalize(Shorts.asList((short[]) object), collection);
1064 } else if (object instanceof boolean[]) {
1065 return normalize(Booleans.asList((boolean[]) object), collection);
1066 } else if (object instanceof char[]) {
1067 return normalize(Chars.asList((char[]) object), collection);
1068 } else {
1069 throw new IllegalArgumentException("Unsupported primitive array type: "
1070 + object.getClass());
1071 }
1072 } else if (object instanceof Iterable<?>) {
1073 boolean changed = false;
1074 for (final Object element : (Iterable<?>) object) {
1075 if (normalize(element, collection)) {
1076 changed = true;
1077 }
1078 }
1079 return changed;
1080 } else {
1081 return collection.add(normalize(object));
1082 }
1083 }
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093 public static void validateIRI(@Nullable final String string) throws IllegalArgumentException {
1094
1095
1096
1097
1098 if (string == null) {
1099 return;
1100 }
1101
1102
1103
1104
1105
1106 for (int i = 0; i < string.length(); ++i) {
1107 final char c = string.charAt(i);
1108 if (c >= 'a' && c <= 'z' || c >= '?' && c <= '[' || c >= '&' && c <= ';' || c == '#'
1109 || c == '$' || c == '!' || c == '=' || c == ']' || c == '_' || c == '~'
1110 || c >= 0xA0 && c <= 0xD7FF || c >= 0xF900 && c <= 0xFDCF || c >= 0xFDF0
1111 && c <= 0xFFEF) {
1112
1113 } else if (c == '%') {
1114 if (i >= string.length() - 2 || Character.digit(string.charAt(i + 1), 16) < 0
1115 || Character.digit(string.charAt(i + 2), 16) < 0) {
1116 throw new IllegalArgumentException("Illegal IRI '" + string
1117 + "' (invalid percent encoding at index " + i + ")");
1118 }
1119 } else {
1120 throw new IllegalArgumentException("Illegal IRI '" + string
1121 + "' (illegal character at index " + i + ")");
1122 }
1123 }
1124 }
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135 @Nullable
1136 public static String cleanIRI(@Nullable final String string) throws IllegalArgumentException {
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146 if (string == null) {
1147 return null;
1148 }
1149
1150
1151
1152
1153 final StringBuilder builder = new StringBuilder();
1154 for (int i = 0; i < string.length(); ++i) {
1155 final char c = string.charAt(i);
1156 if (c >= 'a' && c <= 'z' || c >= '?' && c <= '[' || c >= '&' && c <= ';' || c == '#'
1157 || c == '$' || c == '!' || c == '=' || c == ']' || c == '_' || c == '~'
1158 || c >= 0xA0 && c <= 0xD7FF || c >= 0xF900 && c <= 0xFDCF || c >= 0xFDF0
1159 && c <= 0xFFEF) {
1160 builder.append(c);
1161 } else if (c == '%' && i < string.length() - 2
1162 && Character.digit(string.charAt(i + 1), 16) >= 0
1163 && Character.digit(string.charAt(i + 2), 16) >= 0) {
1164 builder.append('%');
1165 } else {
1166 builder.append('%').append(Character.forDigit(c / 16, 16))
1167 .append(Character.forDigit(c % 16, 16));
1168 }
1169 }
1170
1171
1172 return builder.toString();
1173 }
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190 public static String cleanURI(final String string) throws IllegalArgumentException {
1191
1192
1193
1194
1195
1196
1197 if (string == null) {
1198 return null;
1199 }
1200
1201
1202
1203 final byte[] bytes = string.getBytes(Charset.forName("UTF-8"));
1204
1205
1206
1207
1208 final StringBuilder builder = new StringBuilder();
1209 for (int i = 0; i < bytes.length; ++i) {
1210 final int b = bytes[i] & 0xFF;
1211 if (b >= 'a' && b <= 'z' || b >= '?' && b <= '[' || b >= '&' && b <= ';' || b == '#'
1212 || b == '$' || b == '!' || b == '=' || b == ']' || b == '_' || b == '~') {
1213 builder.append((char) b);
1214 } else if (b == '%' && i < string.length() - 2
1215 && Character.digit(string.charAt(i + 1), 16) >= 0
1216 && Character.digit(string.charAt(i + 2), 16) >= 0) {
1217 builder.append('%');
1218 } else {
1219 builder.append('%').append(Character.forDigit(b / 16, 16))
1220 .append(Character.forDigit(b % 16, 16));
1221 }
1222 }
1223
1224
1225
1226 final java.net.URI uri = java.net.URI.create(builder.toString()).normalize();
1227
1228
1229 if (!uri.isAbsolute()) {
1230 throw new IllegalArgumentException("Not a valid absolute URI: " + uri);
1231 }
1232
1233
1234 return uri.toString();
1235 }
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251 @Nullable
1252 public static Value parseValue(@Nullable final String string,
1253 @Nullable final Map<String, String> namespaces) throws ParseException {
1254
1255 if (string == null) {
1256 return null;
1257 }
1258
1259 try {
1260 final int length = string.length();
1261 if (string.startsWith("\"") || string.startsWith("'")) {
1262 if (string.charAt(length - 1) == '"' || string.charAt(length - 1) == '\'') {
1263 return getValueFactory().createLiteral(string.substring(1, length - 1));
1264 }
1265 int index = string.lastIndexOf("@");
1266 if (index == length - 3) {
1267 final String language = string.substring(index + 1);
1268 if (Character.isLetter(language.charAt(0))
1269 && Character.isLetter(language.charAt(1))) {
1270 return getValueFactory().createLiteral(string.substring(1, index - 1),
1271 language);
1272 }
1273 }
1274 index = string.lastIndexOf("^^");
1275 if (index > 0) {
1276 final String datatype = string.substring(index + 2);
1277 try {
1278 final URI datatypeURI = (URI) parseValue(datatype, namespaces);
1279 return getValueFactory().createLiteral(string.substring(1, index - 1),
1280 datatypeURI);
1281 } catch (final Throwable ex) {
1282
1283 }
1284 }
1285 throw new ParseException(string, "Invalid literal");
1286
1287 } else if (string.startsWith("_:")) {
1288 return getValueFactory().createBNode(string.substring(2));
1289
1290 } else if (string.startsWith("<")) {
1291 return getValueFactory().createURI(string.substring(1, length - 1));
1292
1293 } else if (namespaces != null) {
1294 final int index = string.indexOf(':');
1295 if (index >= 0) {
1296 final String prefix = string.substring(0, index);
1297 final String localName = string.substring(index + 1);
1298 final String namespace = namespaces.get(prefix);
1299 if (namespace != null) {
1300 return getValueFactory().createURI(namespace, localName);
1301 }
1302 }
1303 }
1304 throw new ParseException(string, "Unparseable value");
1305
1306 } catch (final RuntimeException ex) {
1307 throw ex instanceof ParseException ? (ParseException) ex : new ParseException(string,
1308 ex.getMessage(), ex);
1309 }
1310 }
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328 @Nullable
1329 public static String toString(@Nullable final Object object,
1330 @Nullable final Map<String, String> namespaces, final boolean includeProperties) {
1331
1332 if (object instanceof Record) {
1333 return ((Record) object).toString(namespaces, includeProperties);
1334
1335 } else if (object instanceof Statement) {
1336 final Statement statement = (Statement) object;
1337 final Resource subj = statement.getSubject();
1338 final URI pred = statement.getPredicate();
1339 final Value obj = statement.getObject();
1340 final Resource ctx = statement.getContext();
1341 final StringBuilder builder = new StringBuilder();
1342 builder.append('(');
1343 toString(subj, namespaces, builder);
1344 builder.append(',').append(' ');
1345 toString(pred, namespaces, builder);
1346 builder.append(',').append(' ');
1347 toString(obj, namespaces, builder);
1348 builder.append(")");
1349 if (statement.getContext() != null) {
1350 builder.append(' ').append('[');
1351 toString(ctx, namespaces, builder);
1352 builder.append(']');
1353 }
1354 return builder.toString();
1355
1356 } else if (object != null) {
1357 final Value value = convert(object, Value.class);
1358 final StringBuilder builder = new StringBuilder();
1359 toString(value, namespaces, builder);
1360 return builder.toString();
1361 }
1362
1363 return null;
1364 }
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378 public static String toString(final Object object,
1379 @Nullable final Map<String, String> namespaces) {
1380 return toString(object, namespaces, false);
1381 }
1382
1383 private static void toString(final Value value,
1384 @Nullable final Map<String, String> namespaces, final StringBuilder builder) {
1385
1386 if (value instanceof URI) {
1387 final URI uri = (URI) value;
1388 String prefix = null;
1389 if (namespaces != null) {
1390 prefix = namespaceToPrefix(uri.getNamespace(), namespaces);
1391 }
1392 if (prefix != null) {
1393 builder.append(prefix).append(':').append(uri.getLocalName());
1394 } else {
1395 builder.append('<').append(uri.stringValue()).append('>');
1396 }
1397
1398 } else if (value instanceof BNode) {
1399 builder.append('_').append(':').append(((BNode) value).getID());
1400
1401 } else {
1402 final Literal literal = (Literal) value;
1403 builder.append('\"').append(literal.getLabel().replace("\"", "\\\"")).append('\"');
1404 final URI datatype = literal.getDatatype();
1405 if (datatype != null) {
1406 builder.append('^').append('^');
1407 toString(datatype, namespaces, builder);
1408 } else {
1409 final String language = literal.getLanguage();
1410 if (language != null) {
1411 builder.append('@').append(language);
1412 }
1413 }
1414 }
1415 }
1416
1417 private Data() {
1418 }
1419
1420 private static final class TotalOrdering extends Ordering<Object> {
1421
1422 private static final int DT_BOOLEAN = 1;
1423
1424 private static final int DT_STRING = 2;
1425
1426 private static final int DT_LONG = 3;
1427
1428 private static final int DT_DOUBLE = 4;
1429
1430 private static final int DT_DECIMAL = 5;
1431
1432 private static final int DT_CALENDAR = 6;
1433
1434 @Override
1435 public int compare(final Object first, final Object second) {
1436 if (first == null) {
1437 return second == null ? 0 : -1;
1438 } else if (second == null) {
1439 return 1;
1440 } else if (first instanceof URI) {
1441 return compareURI((URI) first, second);
1442 } else if (first instanceof BNode) {
1443 return compareBNode((BNode) first, second);
1444 } else if (first instanceof Record) {
1445 return compareRecord((Record) first, second);
1446 } else if (first instanceof Statement) {
1447 return compareStatement((Statement) first, second);
1448 } else if (first instanceof Literal) {
1449 return compareLiteral((Literal) first, second);
1450 } else {
1451 return compareLiteral(convert(first, Literal.class), second);
1452 }
1453 }
1454
1455 private int compareStatement(final Statement first, final Object second) {
1456 if (second instanceof Statement) {
1457 final Statement secondStmt = (Statement) second;
1458 int result = compare(first.getSubject(), secondStmt.getSubject());
1459 if (result != 0) {
1460 return result;
1461 }
1462 result = compare(first.getPredicate(), secondStmt.getPredicate());
1463 if (result != 0) {
1464 return result;
1465 }
1466 result = compare(first.getObject(), secondStmt.getObject());
1467 if (result != 0) {
1468 return result;
1469 }
1470 result = compare(first.getContext(), secondStmt.getContext());
1471 return result;
1472 }
1473 return -1;
1474 }
1475
1476 private int compareLiteral(final Literal first, final Object second) {
1477 if (second instanceof Resource || second instanceof Record) {
1478 return -1;
1479 } else if (second instanceof Statement) {
1480 return 1;
1481 }
1482 final Literal secondLit = second instanceof Literal ? (Literal) second : convert(
1483 second, Literal.class);
1484 final int firstGroup = classifyDatatype(first.getDatatype());
1485 final int secondGroup = classifyDatatype(secondLit.getDatatype());
1486 switch (firstGroup) {
1487 case DT_BOOLEAN:
1488 if (secondGroup == DT_BOOLEAN) {
1489 return Booleans.compare(first.booleanValue(), secondLit.booleanValue());
1490 }
1491 break;
1492 case DT_STRING:
1493 if (secondGroup == DT_STRING) {
1494 final int result = first.getLabel().compareTo(secondLit.getLabel());
1495 if (result != 0) {
1496 return result;
1497 }
1498 final String firstLang = first.getLanguage();
1499 final String secondLang = secondLit.getLanguage();
1500 if (firstLang == null) {
1501 return secondLang == null ? 0 : -1;
1502 } else {
1503 return secondLang == null ? 1 : firstLang.compareTo(secondLang);
1504 }
1505 }
1506 break;
1507 case DT_LONG:
1508 if (secondGroup == DT_LONG) {
1509 return Longs.compare(first.longValue(), secondLit.longValue());
1510 } else if (secondGroup == DT_DOUBLE) {
1511 return Doubles.compare(first.doubleValue(), secondLit.doubleValue());
1512 } else if (secondGroup == DT_DECIMAL) {
1513 return first.decimalValue().compareTo(secondLit.decimalValue());
1514 }
1515 break;
1516 case DT_DOUBLE:
1517 if (secondGroup == DT_LONG
1518 || secondGroup == DT_DOUBLE) {
1519 return Doubles.compare(first.doubleValue(), secondLit.doubleValue());
1520 } else if (secondGroup == DT_DECIMAL) {
1521 return first.decimalValue().compareTo(secondLit.decimalValue());
1522 }
1523 break;
1524 case DT_DECIMAL:
1525 if (secondGroup == DT_LONG || secondGroup == DT_DOUBLE
1526 || secondGroup == DT_DECIMAL) {
1527 return first.decimalValue().compareTo(secondLit.decimalValue());
1528 }
1529 break;
1530 case DT_CALENDAR:
1531 if (secondGroup == DT_CALENDAR) {
1532 final int result = first.calendarValue().compare(secondLit.calendarValue());
1533 return result == DatatypeConstants.INDETERMINATE ? 0 : result;
1534 }
1535 break;
1536 default:
1537 }
1538 return firstGroup < secondGroup ? -1 : 1;
1539 }
1540
1541 private int compareBNode(final BNode first, final Object second) {
1542 if (second instanceof BNode) {
1543 return first.getID().compareTo(((BNode) second).getID());
1544 } else if (second instanceof URI || second instanceof Record) {
1545 return -1;
1546 }
1547 return 1;
1548 }
1549
1550 private int compareURI(final URI first, final Object second) {
1551 if (second instanceof URI) {
1552 return first.stringValue().compareTo(((URI) second).stringValue());
1553 } else if (second instanceof Record) {
1554 return -1;
1555 }
1556 return 1;
1557 }
1558
1559 private int compareRecord(final Record first, final Object second) {
1560 if (second instanceof Record) {
1561 return first.compareTo((Record) second);
1562 }
1563 return 1;
1564 }
1565
1566 private static int classifyDatatype(final URI datatype) {
1567 if (datatype == null || datatype.equals(XMLSchema.STRING)) {
1568 return DT_STRING;
1569 } else if (datatype.equals(XMLSchema.BOOLEAN)) {
1570 return DT_BOOLEAN;
1571 } else if (datatype.equals(XMLSchema.INT) || datatype.equals(XMLSchema.LONG)
1572 || datatype.equals(XMLSchema.SHORT) || datatype.equals(XMLSchema.BYTE)) {
1573 return DT_LONG;
1574 } else if (datatype.equals(XMLSchema.DOUBLE) || datatype.equals(XMLSchema.FLOAT)) {
1575 return DT_DOUBLE;
1576 } else if (datatype.equals(XMLSchema.DATE) || datatype.equals(XMLSchema.DATETIME)) {
1577 return DT_CALENDAR;
1578 } else if (datatype.equals(XMLSchema.DECIMAL) || datatype.equals(XMLSchema.INTEGER)
1579 || datatype.equals(XMLSchema.NON_NEGATIVE_INTEGER)
1580 || datatype.equals(XMLSchema.POSITIVE_INTEGER)
1581 || datatype.equals(XMLSchema.NEGATIVE_INTEGER)) {
1582 return DT_DECIMAL;
1583 } else if (datatype.equals(XMLSchema.NORMALIZEDSTRING)
1584 || datatype.equals(XMLSchema.TOKEN) || datatype.equals(XMLSchema.NMTOKEN)
1585 || datatype.equals(XMLSchema.LANGUAGE) || datatype.equals(XMLSchema.NAME)
1586 || datatype.equals(XMLSchema.NCNAME)) {
1587 return DT_STRING;
1588 }
1589 throw new IllegalArgumentException("Comparison unsupported for literal datatype "
1590 + datatype);
1591 }
1592
1593 }
1594
1595 private static final class PartialOrdering extends Ordering<Object> {
1596
1597 private final Comparator<Object> totalComparator;
1598
1599 PartialOrdering(final Comparator<Object> totalComparator) {
1600 this.totalComparator = Preconditions.checkNotNull(totalComparator);
1601 }
1602
1603 @Override
1604 public int compare(final Object first, final Object second) {
1605 final int result = this.totalComparator.compare(first, second);
1606 if (result == Integer.MIN_VALUE || result == Integer.MAX_VALUE) {
1607 throw new IllegalArgumentException("Incomparable values: " + first + ", " + second);
1608 }
1609 return result;
1610 }
1611
1612 }
1613
1614 private static final class NamespaceMap extends AbstractMap<String, String> {
1615
1616 private final Map<String, String> namespaces;
1617
1618 private final Map<String, String> prefixes;
1619
1620 private final EntrySet entries;
1621
1622 NamespaceMap() {
1623 this.namespaces = Maps.newHashMap();
1624 this.prefixes = Maps.newHashMap();
1625 this.entries = new EntrySet();
1626 }
1627
1628 @Override
1629 public int size() {
1630 return this.namespaces.size();
1631 }
1632
1633 @Override
1634 public boolean isEmpty() {
1635 return this.namespaces.isEmpty();
1636 }
1637
1638 @Override
1639 public boolean containsKey(final Object prefix) {
1640 return this.namespaces.containsKey(prefix);
1641 }
1642
1643 @Override
1644 public boolean containsValue(final Object namespace) {
1645 return this.prefixes.containsKey(namespace);
1646 }
1647
1648 @Override
1649 public String get(final Object prefix) {
1650 return this.namespaces.get(prefix);
1651 }
1652
1653 public String getPrefix(final Object namespace) {
1654 return this.prefixes.get(namespace);
1655 }
1656
1657 @Override
1658 public String put(final String prefix, final String namespace) {
1659 this.prefixes.put(namespace, prefix);
1660 return this.namespaces.put(prefix, namespace);
1661 }
1662
1663 @Override
1664 public String remove(final Object prefix) {
1665 final String namespace = super.remove(prefix);
1666 removeInverse(namespace, prefix);
1667 return namespace;
1668 }
1669
1670 private void removeInverse(@Nullable final String namespace, final Object prefix) {
1671 if (namespace == null) {
1672 return;
1673 }
1674 final String inversePrefix = this.prefixes.remove(namespace);
1675 if (!prefix.equals(inversePrefix)) {
1676 this.prefixes.put(namespace, inversePrefix);
1677 } else if (this.prefixes.size() != this.namespaces.size()) {
1678 for (final Map.Entry<String, String> entry : this.namespaces.entrySet()) {
1679 if (entry.getValue().equals(namespace)) {
1680 this.prefixes.put(entry.getValue(), entry.getKey());
1681 break;
1682 }
1683 }
1684 }
1685 }
1686
1687 @Override
1688 public void clear() {
1689 this.namespaces.clear();
1690 this.prefixes.clear();
1691 }
1692
1693 @Override
1694 public Set<Entry<String, String>> entrySet() {
1695 return this.entries;
1696 }
1697
1698 final class EntrySet extends AbstractSet<Map.Entry<String, String>> {
1699
1700 @Override
1701 public int size() {
1702 return NamespaceMap.this.namespaces.size();
1703 }
1704
1705 @Override
1706 public Iterator<Entry<String, String>> iterator() {
1707 final Iterator<Entry<String, String>> iterator = NamespaceMap.this.namespaces
1708 .entrySet().iterator();
1709 return new Iterator<Entry<String, String>>() {
1710
1711 private Entry<String, String> last;
1712
1713 @Override
1714 public boolean hasNext() {
1715 return iterator.hasNext();
1716 }
1717
1718 @Override
1719 public Entry<String, String> next() {
1720 this.last = new EntryWrapper(iterator.next());
1721 return this.last;
1722 }
1723
1724 @Override
1725 public void remove() {
1726 iterator.remove();
1727 removeInverse(this.last.getValue(), this.last.getKey());
1728 }
1729
1730 };
1731 }
1732
1733 private class EntryWrapper implements Entry<String, String> {
1734
1735 private final Entry<String, String> entry;
1736
1737 EntryWrapper(final Entry<String, String> entry) {
1738 this.entry = entry;
1739 }
1740
1741 @Override
1742 public String getKey() {
1743 return this.entry.getKey();
1744 }
1745
1746 @Override
1747 public String getValue() {
1748 return this.entry.getValue();
1749 }
1750
1751 @Override
1752 public String setValue(final String namespace) {
1753 final String oldNamespace = this.entry.getValue();
1754 if (!Objects.equal(oldNamespace, namespace)) {
1755 final String prefix = this.entry.getKey();
1756 removeInverse(oldNamespace, prefix);
1757 this.entry.setValue(namespace);
1758 NamespaceMap.this.prefixes.put(namespace, prefix);
1759 }
1760 return oldNamespace;
1761 }
1762
1763 @Override
1764 public boolean equals(final Object object) {
1765 return this.entry.equals(object);
1766 }
1767
1768 @Override
1769 public int hashCode() {
1770 return this.entry.hashCode();
1771 }
1772
1773 @Override
1774 public String toString() {
1775 return this.entry.toString();
1776 }
1777
1778 }
1779 }
1780
1781 }
1782
1783 private static final class NamespaceCombinedMap extends AbstractMap<String, String> {
1784
1785 final Map<String, String> primaryNamespaces;
1786
1787 final Map<String, String> secondaryNamespaces;
1788
1789 NamespaceCombinedMap(final Map<String, String> primaryNamespaces,
1790 final Map<String, String> secondaryNamespaces) {
1791
1792 this.primaryNamespaces = primaryNamespaces;
1793 this.secondaryNamespaces = secondaryNamespaces;
1794 }
1795
1796 @Override
1797 public String get(final Object prefix) {
1798 String uri = this.primaryNamespaces.get(prefix);
1799 if (uri == null) {
1800 uri = this.secondaryNamespaces.get(prefix);
1801 }
1802 return uri;
1803 }
1804
1805 @Override
1806 public String put(final String prefix, final String uri) {
1807 return this.primaryNamespaces.put(prefix, uri);
1808 }
1809
1810 @Override
1811 public Set<Map.Entry<String, String>> entrySet() {
1812 return new EntrySet();
1813 }
1814
1815 @Override
1816 public void clear() {
1817 this.primaryNamespaces.clear();
1818 }
1819
1820 final class EntrySet extends AbstractSet<Map.Entry<String, String>> {
1821
1822 @Override
1823 public int size() {
1824 return Sets.union(NamespaceCombinedMap.this.primaryNamespaces.keySet(),
1825 NamespaceCombinedMap.this.secondaryNamespaces.keySet()).size();
1826 }
1827
1828 @Override
1829 public Iterator<Entry<String, String>> iterator() {
1830
1831 final Set<String> additionalKeys = Sets.difference(
1832 NamespaceCombinedMap.this.secondaryNamespaces.keySet(),
1833 NamespaceCombinedMap.this.primaryNamespaces.keySet());
1834
1835 Function<String, Entry<String, String>> transformer;
1836 transformer = new Function<String, Entry<String, String>>() {
1837
1838 @Override
1839 public Entry<String, String> apply(final String prefix) {
1840 return new AbstractMap.SimpleImmutableEntry<String, String>(prefix,
1841 NamespaceCombinedMap.this.secondaryNamespaces.get(prefix));
1842 }
1843
1844 };
1845
1846 return Iterators.concat(NamespaceCombinedMap.this.primaryNamespaces.entrySet()
1847 .iterator(), ignoreRemove(Iterators.transform(additionalKeys.iterator(),
1848 transformer)));
1849 }
1850
1851 private <T> Iterator<T> ignoreRemove(final Iterator<T> iterator) {
1852 return new Iterator<T>() {
1853
1854 @Override
1855 public boolean hasNext() {
1856 return iterator.hasNext();
1857 }
1858
1859 @Override
1860 public T next() {
1861 return iterator.next();
1862 }
1863
1864 @Override
1865 public void remove() {
1866 }
1867
1868 };
1869 }
1870
1871 }
1872
1873 }
1874
1875 }