1 package org.opentrafficsim.editor;
2
3 import java.io.IOException;
4 import java.net.URI;
5 import java.net.URISyntaxException;
6 import java.util.ArrayList;
7 import java.util.Iterator;
8 import java.util.LinkedHashMap;
9 import java.util.LinkedHashSet;
10 import java.util.LinkedList;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Map.Entry;
14 import java.util.Queue;
15 import java.util.Set;
16
17 import javax.xml.parsers.ParserConfigurationException;
18
19 import org.djutils.exceptions.Throw;
20 import org.w3c.dom.Document;
21 import org.w3c.dom.Node;
22 import org.xml.sax.SAXException;
23
24
25
26
27
28
29
30
31
32
33 public class Schema
34 {
35
36
37 private Node root;
38
39
40 private final Set<String> readFiles = new LinkedHashSet<>();
41
42
43 private final Map<String, Node> types = new LinkedHashMap<>();
44
45
46 private final Map<String, Set<String>> extendedTypes = new LinkedHashMap<>();
47
48
49
50
51
52 private final Map<String, Set<String>> referredTypes = new LinkedHashMap<>();
53
54
55 private final Map<String, Node> elements = new LinkedHashMap<>();
56
57
58 private final Map<String, Set<String>> referredElements = new LinkedHashMap<>();
59
60
61 private final Map<String, String> documentation = new LinkedHashMap<>();
62
63
64 private final Map<String, Node> keys = new LinkedHashMap<>();
65
66
67 private final Map<String, Node> keyrefs = new LinkedHashMap<>();
68
69
70 private final Map<String, Node> uniques = new LinkedHashMap<>();
71
72
73 private final Queue<RecursionElement> queue = new LinkedList<>();
74
75
76 private boolean blockLoop = false;
77
78
79
80
81
82 public Schema(final Document document)
83 {
84 this.readFiles.add(document.getDocumentURI());
85
86 queue("", document, true);
87 while (!this.queue.isEmpty())
88 {
89 RecursionElement next = this.queue.poll();
90 read(next.getPath(), next.getNode(), next.isExtendPath());
91 }
92
93
94 for (Entry<String, Node> entry : this.elements.entrySet())
95 {
96 String referredTypeName = DocumentReader.getAttribute(entry.getValue(), "type");
97 if (referredTypeName != null && !referredTypeName.startsWith("xsd:"))
98 {
99 Node referredType = getType(referredTypeName);
100 entry.setValue(referredType);
101 }
102 }
103
104
105 Set<String> allTypes = new LinkedHashSet<>(this.types.keySet());
106 for (String str : this.extendedTypes.keySet())
107 {
108 allTypes.removeIf((val) -> val.startsWith(str));
109 }
110 for (String str : this.referredTypes.keySet())
111 {
112 allTypes.removeIf((val) -> val.startsWith(str));
113 }
114 if (!allTypes.isEmpty())
115 {
116 System.out.println(allTypes.size() + " types are defined but never extended or referred to.");
117
118 }
119
120 Set<String> allElements = new LinkedHashSet<>(this.elements.keySet());
121 for (String str : this.referredElements.keySet())
122 {
123 allElements.removeIf((val) -> val.startsWith(str));
124 }
125 for (String str : this.types.keySet())
126 {
127 allElements.removeIf((val) -> val.startsWith(str));
128 }
129 allElements.removeIf((path) -> path.startsWith("Ots"));
130 if (!allElements.isEmpty())
131 {
132 System.out.println(allElements.size() + " elements are defined but never referred to, nor are they a type.");
133
134 }
135
136 checkKeys();
137 checkKeyrefs();
138 checkUniques();
139
140
141
142
143
144 System.out.println("Root found as '" + DocumentReader.getAttribute(this.getRoot(), "name") + "'.");
145 System.out.println("Read " + this.readFiles.size() + " files.");
146 System.out.println("Read " + this.elements.size() + " elements.");
147 System.out.println("Read " + this.types.size() + " types.");
148 System.out.println("Read " + this.extendedTypes.size() + " extended types.");
149 System.out.println("Read " + this.documentation.size() + " documentations.");
150 System.out.println("Read " + this.keys.size() + " keys.");
151 System.out.println("Read " + this.keyrefs.size() + " keyrefs.");
152 System.out.println("Read " + this.uniques.size() + " uniques.");
153 for (String type : this.extendedTypes.keySet())
154 {
155 if (!this.types.containsKey(type) && !type.startsWith("xsd:"))
156 {
157 System.err.println("Type '" + type + "' is extended but was not found.");
158 }
159 }
160 }
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187 private void read(final String path, final Node node, final boolean extendPath)
188 {
189 if (recursion(path))
190 {
191 System.out.println("Recursion found at " + path + ", further expansion is halted.");
192 return;
193 }
194
195 if (node.getNodeName().equals("xsd:extension"))
196 {
197 String base = DocumentReader.getAttribute(node, "base").replace("ots:", "");
198 if (!base.startsWith("xsd:"))
199 {
200 Node baseNode = getType(base);
201 if (baseNode == null)
202 {
203 if (this.blockLoop)
204 {
205 return;
206 }
207 this.blockLoop = true;
208
209 queue(path, node, false);
210 }
211 else
212 {
213 queue(path, baseNode, false);
214 }
215 }
216 if (this.extendedTypes.containsKey(base) && this.extendedTypes.get(base).contains(path))
217 {
218
219 return;
220 }
221 this.extendedTypes.computeIfAbsent(base, (key) -> new LinkedHashSet<String>()).add(path);
222 }
223 this.blockLoop = false;
224
225 String nextPath = path;
226 Node nextNode = node;
227 if (node.getNodeName().equals("xsd:element") && node.hasAttributes())
228 {
229
230 String name = DocumentReader.getAttribute(node, "name");
231 if (name != null)
232 {
233 if (name.equals("Ots"))
234 {
235 this.root = node;
236 }
237 nextPath = extendPath ? (nextPath.isEmpty() ? name : nextPath + "." + name) : nextPath;
238 this.elements.put(nextPath, node);
239 }
240 String ref = DocumentReader.getAttribute(node, "ref");
241 if (ref != null)
242 {
243 ref = ref.replace("ots:", "");
244 nextNode = getElement(ref);
245
246
247
248
249
250 if (DocumentReader.getAttribute(nextNode, "type") != null)
251 {
252 element(path, nextNode);
253 }
254 nextPath = extendPath ? (nextPath.isEmpty() ? ref : nextPath + "." + ref) : nextPath;
255 this.elements.put(nextPath, nextNode);
256 Throw.whenNull(nextNode, "Element %s refers to a type that was not loaded in first pass.", nextPath);
257 }
258 }
259
260 String name = DocumentReader.getAttribute(nextNode, "name");
261 String nodeName = nextNode.getNodeName();
262 if (name != null && (nodeName.equals("xsd:complexType") || nodeName.equals("xsd:simpleType")))
263 {
264 this.types.put(name, nextNode);
265 nextPath = extendPath ? (nextPath.isEmpty() ? name : nextPath + "." + name) : nextPath;
266 }
267
268 if (!nextNode.hasChildNodes())
269 {
270 return;
271 }
272 for (int childIndex = 0; childIndex < nextNode.getChildNodes().getLength(); childIndex++)
273 {
274 Node child = nextNode.getChildNodes().item(childIndex);
275 switch (child.getNodeName())
276 {
277 case "#text":
278 break;
279 case "xsd:include":
280 include(nextPath, child);
281 break;
282 case "xsd:element":
283 element(nextPath, child);
284 break;
285 case "xsd:attribute":
286 attribute(nextPath, child);
287 break;
288 case "xsd:documentation":
289 documentation(nextPath, child);
290 break;
291 case "xsd:union":
292 union(nextPath, child);
293 break;
294 case "xsd:key":
295 if (nextPath.startsWith("Ots"))
296 {
297 this.keys.put(nextPath + "." + DocumentReader.getAttribute(child, "name"), child);
298 }
299 break;
300 case "xsd:keyref":
301 if (nextPath.startsWith("Ots"))
302 {
303 this.keyrefs.put(nextPath + "." + DocumentReader.getAttribute(child, "name"), child);
304 }
305 break;
306 case "xsd:unique":
307 if (nextPath.startsWith("Ots"))
308 {
309 this.uniques.put(nextPath + "." + DocumentReader.getAttribute(child, "name"), child);
310 }
311 break;
312 default:
313 read(nextPath, child, true);
314 }
315 }
316 }
317
318
319
320
321
322
323
324 private boolean recursion(final String path)
325 {
326 StringBuffer sub = (new StringBuffer(path)).reverse();
327 int fromIndex = 0;
328 while (fromIndex < sub.length())
329 {
330 int dot = sub.indexOf(".", fromIndex);
331 if (dot < 0)
332 {
333 return false;
334 }
335 int toIndex = 2 * (dot + 1);
336 if (toIndex > sub.length())
337 {
338 return false;
339 }
340 if (sub.substring(0, dot + 1).equals(sub.substring(dot + 1, toIndex)))
341 {
342 return true;
343 }
344 fromIndex = dot + 1;
345 }
346 return false;
347 }
348
349
350
351
352
353
354
355
356
357 private void queue(final String path, final Node node, final boolean extendPath)
358 {
359 this.queue.add(new RecursionElement(path, node, extendPath));
360 }
361
362
363
364
365
366
367 private void include(final String path, final Node node)
368 {
369 String schemaLocation = DocumentReader.getAttribute(node, "schemaLocation");
370 String schemaPath = folder(node) + schemaLocation;
371 if (!this.readFiles.add(schemaPath))
372 {
373 return;
374 }
375 try
376 {
377 read(path, DocumentReader.open(new URI(schemaPath)), true);
378 }
379 catch (SAXException | IOException | ParserConfigurationException | URISyntaxException e)
380 {
381 throw new RuntimeException("Unable to find resource " + folder(node) + schemaLocation);
382 }
383 }
384
385
386
387
388
389
390 private String folder(final Node node)
391 {
392 String uri = node.getBaseURI();
393 if (uri == null)
394 {
395 return "";
396 }
397 int a = uri.lastIndexOf("\\");
398 int b = uri.lastIndexOf("/");
399 return uri.substring(0, (a > b ? a : b) + 1);
400 }
401
402
403
404
405
406
407 private void element(final String path, final Node node)
408 {
409 if (DocumentReader.getAttribute(node, "ref") != null)
410 {
411 ref(path, node);
412 return;
413 }
414 String type = DocumentReader.getAttribute(node, "type");
415 if (type != null && !type.startsWith("xsd:"))
416 {
417 type = type.replace("ots:", "");
418 this.referredTypes.computeIfAbsent(type, (key) -> new LinkedHashSet<>()).add(path.isEmpty()
419 ? DocumentReader.getAttribute(node, "name") : path + "." + DocumentReader.getAttribute(node, "name"));
420 Node referred = getType(type);
421 if (referred == null)
422 {
423 queue(path, node, true);
424 return;
425 }
426 else
427 {
428 queue(path + "." + DocumentReader.getAttribute(node, "name"), referred, false);
429 }
430 }
431 read(path, node, true);
432 }
433
434
435
436
437
438
439
440
441 private void ref(final String path, final Node node)
442 {
443 String ref = DocumentReader.getAttribute(node, "ref").replace("ots:", "");
444 if (ref.equals("xi:include"))
445 {
446 return;
447 }
448 this.referredElements.computeIfAbsent(ref, (key) -> new LinkedHashSet<>()).add(path);
449 Node refNode = getElement(ref);
450 if (refNode == null)
451 {
452 queue(path, node, true);
453 }
454 else
455 {
456 read(path, node, true);
457 }
458 }
459
460
461
462
463
464
465 private void attribute(final String path, final Node node)
466 {
467 if (DocumentReader.getAttribute(node, "type") != null)
468 {
469 String type = DocumentReader.getAttribute(node, "type").replace("ots:", "");
470 String name = DocumentReader.getAttribute(node, "name");
471 this.referredTypes.computeIfAbsent(type, (key) -> new LinkedHashSet<>()).add(path + "." + name);
472 }
473 read(path, node, true);
474 }
475
476
477
478
479
480
481 private void documentation(final String path, final Node node)
482 {
483 this.documentation.put(path, DocumentReader.getChild(node, "#text").getNodeValue().trim().replaceAll("\r\n", " ")
484 .replaceAll("\n", " ").replaceAll("\r", " ").replace(" ", ""));
485 }
486
487
488
489
490
491
492 private void union(final String path, final Node node)
493 {
494 for (String type : DocumentReader.getAttribute(node, "memberTypes").split(" "))
495 {
496 this.referredTypes.computeIfAbsent(type.replace("ots:", ""), (key) -> new LinkedHashSet<>()).add(path);
497 }
498 read(path, node, true);
499 }
500
501
502
503
504
505
506
507
508
509
510 private class RecursionElement
511 {
512
513 private final String path;
514
515
516 private final Node node;
517
518
519 private final boolean extendPath;
520
521
522
523
524
525
526
527 RecursionElement(final String path, final Node node, final boolean extendPath)
528 {
529 this.path = path;
530 this.node = node;
531 this.extendPath = extendPath;
532 }
533
534
535
536
537
538 public String getPath()
539 {
540 return this.path;
541 }
542
543
544
545
546
547 public Node getNode()
548 {
549 return this.node;
550 }
551
552
553
554
555
556 public boolean isExtendPath()
557 {
558 return this.extendPath;
559 }
560
561 }
562
563
564
565
566
567 private void checkKeys()
568 {
569 checkKeyOrUniques("Key", this.keys);
570 }
571
572
573
574
575
576 private void checkKeyrefs()
577 {
578 for (String fullPath : this.keyrefs.keySet())
579 {
580 Node node = this.keyrefs.get(fullPath);
581 String keyref = DocumentReader.getAttribute(node, "name");
582 String keyName = DocumentReader.getAttribute(node, "refer").replace("ots:", "");
583 Node key = null;
584 boolean keyFound = false;
585 Iterator<Node> iterator = this.keys.values().iterator();
586 while (!keyFound && iterator.hasNext())
587 {
588 key = iterator.next();
589 keyFound = keyName.equals(DocumentReader.getAttribute(key, "name"));
590 }
591 if (!keyFound)
592 {
593 System.out.println(
594 "Keyref " + keyref + " refers to non existing key " + DocumentReader.getAttribute(node, "refer") + ".");
595 }
596 String context = fullPath.substring(0, fullPath.lastIndexOf("."));
597 List<Node> elements = getSelectedElements(context, node);
598 if (elements.isEmpty())
599 {
600 System.out.println("Keyref " + keyref + " (" + getXpath(node) + ") not found among elements.");
601 }
602 else
603 {
604 for (Node selected : elements)
605 {
606 for (Node field : DocumentReader.getChildren(node, "xsd:field"))
607 {
608 String xpathFieldString = DocumentReader.getAttribute(field, "xpath");
609 boolean found = false;
610 for (String xpathField : xpathFieldString.split("\\|"))
611 {
612 found = followXPath(selected, xpathField);
613 }
614 if (!found)
615 {
616 System.out.println("Keyref " + keyref + " (" + getXpath(node) + ") points to non existing field '"
617 + xpathFieldString + "'.");
618 }
619 }
620 }
621 }
622 }
623 }
624
625
626
627
628
629
630
631 private boolean followXPath(final Node selected, final String xpath)
632 {
633 if (xpath.startsWith("@"))
634 {
635 String xpathField = xpath.substring(1);
636 return hasElementAttribute(selected, xpathField);
637 }
638 if (xpath.equals(".")
639 && (selected.getNodeName().equals("xsd:simpleType") || selected.getNodeName().equals("xsd:element")))
640 {
641 return true;
642 }
643 int index = xpath.indexOf("/");
644 String name = index < 0 ? xpath : xpath.substring(0, index);
645 String remainder = index < 0 ? null : xpath.substring(index);
646 name = name.replace("ots:", "");
647 boolean found = false;
648 if (name.equals(DocumentReader.getAttribute(selected, "name")))
649 {
650 found = true;
651 }
652 else if (selected.getNodeName().equals("xsd:complexType") || selected.getNodeName().equals("xsd:sequence")
653 || selected.getNodeName().equals("xsd:choice") || selected.getNodeName().equals("xsd:all")
654 || selected.getNodeName().equals("xsd:element"))
655 {
656 for (int i = 0; i < selected.getChildNodes().getLength() && !found; i++)
657 {
658 Node child = selected.getChildNodes().item(i);
659 if (selected.getNodeName().equals("xsd:complexType") || selected.getNodeName().equals("xsd:sequence")
660 || selected.getNodeName().equals("xsd:choice") || selected.getNodeName().equals("xsd:all")
661 || selected.getNodeName().equals("xsd:element"))
662 {
663 found = followXPath(child, name);
664 }
665 }
666 }
667 if (found && remainder != null)
668 {
669 for (int i = 0; i < selected.getChildNodes().getLength() && !found; i++)
670 {
671 Node child = selected.getChildNodes().item(i);
672 if (selected.getNodeName().equals("xsd:simpleType") || selected.getNodeName().equals("xsd:complexType")
673 || selected.getNodeName().equals("xsd:element"))
674 {
675 found = followXPath(child, remainder);
676 }
677 }
678 }
679 return found;
680 }
681
682
683
684
685
686 private void checkUniques()
687 {
688 checkKeyOrUniques("Unique", this.uniques);
689 }
690
691
692
693
694
695
696
697 private void checkKeyOrUniques(final String label, final Map<String, Node> map)
698 {
699 for (String fullPath : map.keySet())
700 {
701 Node node = map.get(fullPath);
702 String context = fullPath.substring(0, fullPath.lastIndexOf("."));
703 String element = DocumentReader.getAttribute(node, "name");
704 for (String selector : getXpath(node).split("\\|"))
705 {
706 String path;
707 Node selected = null;
708 if (!selector.startsWith(".//"))
709 {
710 path = context + "." + selector.replace("/", ".");
711 selected = getElement(path);
712 }
713 else
714 {
715
716 path = context + selector.replace(".//", ".{...}").replace("/", ".");
717 for (Entry<String, Node> entry : this.elements.entrySet())
718 {
719 String elementPath = entry.getKey();
720 if (elementPath.startsWith(context) && elementPath.endsWith(selector.substring(3).replace("/", ".")))
721 {
722 selected = entry.getValue();
723 break;
724 }
725 }
726 }
727 if (selected == null)
728 {
729 System.out.println(label + " " + element + " (" + path + ") not found among elements.");
730 }
731 else
732 {
733 for (Node field : DocumentReader.getChildren(node, "xsd:field"))
734 {
735 String xpathFieldString = DocumentReader.getAttribute(field, "xpath");
736 boolean found = false;
737 for (String xpathField : xpathFieldString.split("\\|"))
738 {
739 if (xpathField.startsWith("@"))
740 {
741 xpathField = xpathField.substring(1);
742 if (hasElementAttribute(selected, xpathField))
743 {
744 found = true;
745 }
746 }
747 }
748 if (!found)
749 {
750 System.out.println(label + " " + element + " (" + path + ") points to non existing field "
751 + xpathFieldString + ".");
752 }
753 }
754 }
755 }
756 }
757 }
758
759
760
761
762
763
764
765 private List<Node> getSelectedElements(final String context, final Node node)
766 {
767 List<Node> nodes = new ArrayList<>();
768 for (String selector : getXpath(node).split("\\|"))
769 {
770 Node selected = null;
771 if (!selector.startsWith(".//"))
772 {
773 selected = getElement(context + "." + selector.replace("/", "."));
774 }
775 else
776 {
777
778 for (Entry<String, Node> entry : this.elements.entrySet())
779 {
780 String elementPath = entry.getKey();
781 if (elementPath.startsWith(context) && elementPath.endsWith(selector.replace(".//", "").replace("/", ".")))
782 {
783 selected = entry.getValue();
784 break;
785 }
786 }
787 }
788 if (selected != null)
789 {
790 nodes.add(selected);
791 }
792 else
793 {
794 for (Entry<String, Node> entry : this.elements.entrySet())
795 {
796 if (!entry.getKey().startsWith("Ots.")
797 && entry.getKey().endsWith(selector.replace(".//", "").replace("/", ".")))
798 {
799 nodes.add(entry.getValue());
800 }
801 else if (isType(entry.getValue(), selector.replace(".//", "").replace("/", ".")))
802 {
803 nodes.add(entry.getValue());
804 }
805 }
806 }
807 }
808 return nodes;
809 }
810
811
812
813
814
815
816 private String getXpath(final Node node)
817 {
818 Node child = DocumentReader.getChild(node, "xsd:selector");
819 String xpath = DocumentReader.getAttribute(child, "xpath");
820 xpath = xpath.replace("ots:", "");
821 return xpath;
822 }
823
824
825
826
827
828
829
830
831
832
833 private boolean hasElementAttribute(final Node node, final String name)
834 {
835 if (node.getNodeName().equals("xsd:complexType"))
836 {
837
838 return hasElementAttribute(node, name, null);
839 }
840
841 return hasElementAttribute(node, name, "xsd:complexType");
842 }
843
844
845
846
847
848
849
850
851
852
853
854 private boolean hasElementAttribute(final Node node, final String name, final String viaType)
855 {
856 Node via = viaType == null ? node : DocumentReader.getChild(node, viaType);
857 for (int childIndex = 0; childIndex < via.getChildNodes().getLength(); childIndex++)
858 {
859 Node child = via.getChildNodes().item(childIndex);
860 String childName = DocumentReader.getAttribute(child, "name");
861 if (child.getNodeName().equals("xsd:attribute") && name.equals(childName))
862 {
863 return true;
864 }
865 if (child.getNodeName().equals("xsd:sequence"))
866 {
867 boolean inSub = hasElementAttribute(child, name, null);
868 if (inSub)
869 {
870 return true;
871 }
872 }
873 if (child.getNodeName().equals("xsd:complexContent") || child.getNodeName().equals("xsd:simpleContent"))
874 {
875 Node extension = DocumentReader.getChild(child, "xsd:extension");
876 String base = DocumentReader.getAttribute(extension, "base");
877 if (base != null)
878 {
879 Node baseNode = getType(base);
880 boolean has = hasElementAttribute(baseNode, name, null);
881 if (has)
882 {
883 return has;
884 }
885 }
886 boolean has = hasElementAttribute(extension, name, null);
887 if (has)
888 {
889 return has;
890 }
891 }
892 }
893 return false;
894 }
895
896
897
898
899
900 public Node getRoot()
901 {
902 return this.root;
903 }
904
905
906
907
908
909
910 public Node getElement(final String path)
911 {
912 return this.elements.get(path.replace("ots:", ""));
913 }
914
915
916
917
918
919
920 public Node getType(final String base)
921 {
922 return this.types.get(base.replace("ots:", ""));
923 }
924
925
926
927
928
929 public Map<String, Node> keys()
930 {
931 return new LinkedHashMap<>(this.keys);
932 }
933
934
935
936
937
938 public Map<String, Node> keyrefs()
939 {
940 return new LinkedHashMap<>(this.keyrefs);
941 }
942
943
944
945
946
947 public Map<String, Node> uniques()
948 {
949 return new LinkedHashMap<>(this.uniques);
950 }
951
952
953
954
955
956
957
958 public boolean isType(final Node node, final String path)
959 {
960 String name = DocumentReader.getAttribute(node, "name");
961 if (path.equals(name))
962 {
963 return true;
964 }
965 Node nodeUse = node;
966 if (nodeUse.getNodeName().equals("xsd:element"))
967 {
968 nodeUse = DocumentReader.getChild(node, "xsd:complexType");
969 if (nodeUse == null)
970 {
971 nodeUse = DocumentReader.getChild(node, "xsd:simpleType");
972 if (nodeUse == null)
973 {
974 return false;
975 }
976 }
977 }
978 for (int childIndex = 0; childIndex < nodeUse.getChildNodes().getLength(); childIndex++)
979 {
980 Node child = nodeUse.getChildNodes().item(childIndex);
981 if (child.getNodeName().equals("xsd:complexContent") || child.getNodeName().equals("xsd:simpleContent"))
982 {
983 String base = null;
984 Node extension = DocumentReader.getChild(child, "xsd:extension");
985 if (extension != null)
986 {
987 base = DocumentReader.getAttribute(extension, "base");
988
989 }
990 Node restriction = DocumentReader.getChild(child, "xsd:restriction");
991 if (restriction != null)
992 {
993 base = DocumentReader.getAttribute(restriction, "base");
994 }
995 boolean isType = base.endsWith(path);
996 if (isType)
997 {
998 return isType;
999 }
1000 if (base != null && !base.startsWith("xsd:"))
1001 {
1002 Node baseNode = getType(base);
1003 if (baseNode != null && !baseNode.equals(nodeUse))
1004 {
1005 return isType(baseNode, path);
1006 }
1007 }
1008 }
1009 }
1010 return false;
1011 }
1012
1013 }