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 }