1 /*
2  * Copyright (c) 2016 Helmut Neemann
3  * Use of this source code is governed by the GPL v3 license
4  * that can be found in the LICENSE file.
5  */
6 package de.neemann.digital.draw.library;
7 
8 import de.neemann.digital.core.arithmetic.*;
9 import de.neemann.digital.core.arithmetic.Comparator;
10 import de.neemann.digital.core.basic.*;
11 import de.neemann.digital.core.element.ElementAttributes;
12 import de.neemann.digital.core.element.ElementTypeDescription;
13 import de.neemann.digital.core.element.Keys;
14 import de.neemann.digital.core.extern.External;
15 import de.neemann.digital.core.flipflops.*;
16 import de.neemann.digital.core.io.*;
17 import de.neemann.digital.core.memory.*;
18 import de.neemann.digital.core.pld.DiodeBackward;
19 import de.neemann.digital.core.pld.DiodeForward;
20 import de.neemann.digital.core.pld.PullDown;
21 import de.neemann.digital.core.pld.PullUp;
22 import de.neemann.digital.core.switching.*;
23 import de.neemann.digital.core.wiring.*;
24 import de.neemann.digital.draw.elements.Circuit;
25 import de.neemann.digital.draw.elements.PinException;
26 import de.neemann.digital.draw.elements.Tunnel;
27 import de.neemann.digital.draw.shapes.ShapeFactory;
28 import de.neemann.digital.gui.Settings;
29 import de.neemann.digital.gui.components.data.DummyElement;
30 import de.neemann.digital.gui.components.data.ScopeTrigger;
31 import de.neemann.digital.gui.components.graphics.GraphicCard;
32 import de.neemann.digital.gui.components.graphics.LedMatrix;
33 import de.neemann.digital.gui.components.graphics.VGA;
34 import de.neemann.digital.gui.components.terminal.Keyboard;
35 import de.neemann.digital.gui.components.terminal.Terminal;
36 import de.neemann.digital.lang.Lang;
37 import de.neemann.digital.testing.TestCaseElement;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40 
41 import java.io.File;
42 import java.io.FileNotFoundException;
43 import java.io.IOException;
44 import java.net.URISyntaxException;
45 import java.util.*;
46 
47 /**
48  * The ElementLibrary is responsible for storing all the components which can be used in a circuit.
49  * Also the import of nested circuits is handled in this class.
50  * This import works in two steps: At first all the files in the same directory as the root circuit are loaded.
51  * The file names are shown in the components menu. From there you can pick a file to insert it to the circuit.
52  * When a file is selected it is loaded to the library. After that also an icon is available.
53  * This is done because the loading of a circuit and the creation of an icon is very time consuming and should
54  * be avoided if not necessary. It's a kind of lazy loading.
55  */
56 public class ElementLibrary implements Iterable<ElementLibrary.ElementContainer>, LibraryInterface {
57     private static final Logger LOGGER = LoggerFactory.getLogger(ElementLibrary.class);
58     private static final long MIN_RESCAN_INTERVAL = 5000;
59 
60     /**
61      * @return the additional library path
62      */
getLibPath()63     public static File getLibPath() {
64         String path;
65         try {
66             path = ElementLibrary.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath().replace('\\', '/');
67         } catch (URISyntaxException e) {
68             return new File("noLibFound");
69         }
70         if (path.endsWith("/target/classes/"))
71             return toCanonical(new File(path.substring(0, path.length() - 16) + "/src/main/dig/lib"));
72         if (path.endsWith("/target/Digital.jar"))
73             return new File(path.substring(0, path.length() - 19) + "/src/main/dig/lib");
74         if (path.endsWith("Digital.jar"))
75             return new File(path.substring(0, path.length() - 12) + "/lib");
76 
77         return new File("noLibFound");
78     }
79 
toCanonical(File file)80     private static File toCanonical(File file) {
81         try {
82             return file.getCanonicalFile();
83         } catch (IOException e) {
84             return file;
85         }
86     }
87 
88     private final HashMap<String, LibraryNode> map = new HashMap<>();
89     private final HashSet<String> isProgrammable = new HashSet<>();
90     private final ArrayList<LibraryListener> listeners = new ArrayList<>();
91     private final LibraryNode root;
92     private final ElementLibraryFolder custom;
93     private JarComponentManager jarComponentManager;
94     private ShapeFactory shapeFactory;
95     private File rootLibraryPath;
96     private Exception exception;
97     private long lastRescanTime;
98     private StringBuilder warningMessage;
99 
100     /**
101      * Creates a new instance.
102      */
ElementLibrary()103     public ElementLibrary() {
104         this(null);
105     }
106 
107     /**
108      * Creates a new instance.
109      *
110      * @param jarFile the jar file to load
111      */
ElementLibrary(File jarFile)112     public ElementLibrary(File jarFile) {
113         root = new LibraryNode(Lang.get("menu_elements"))
114                 .setLibrary(this)
115                 .add(new LibraryNode(Lang.get("lib_Logic"))
116                         .add(And.DESCRIPTION)
117                         .add(NAnd.DESCRIPTION)
118                         .add(Or.DESCRIPTION)
119                         .add(NOr.DESCRIPTION)
120                         .add(XOr.DESCRIPTION)
121                         .add(XNOr.DESCRIPTION)
122                         .add(Not.DESCRIPTION)
123                         .add(LookUpTable.DESCRIPTION))
124                 .add(new LibraryNode(Lang.get("lib_io"))
125                         .add(Out.DESCRIPTION)
126                         .add(Out.LEDDESCRIPTION)
127                         .add(In.DESCRIPTION)
128                         .add(Clock.DESCRIPTION)
129                         .add(Button.DESCRIPTION)
130                         .add(DipSwitch.DESCRIPTION)
131                         .add(DummyElement.TEXTDESCRIPTION)
132                         .add(Probe.DESCRIPTION)
133                         .add(DummyElement.DATADESCRIPTION)
134                         .add(ScopeTrigger.DESCRIPTION)
135                         .add(new LibraryNode(Lang.get("lib_displays"))
136                                 .add(RGBLED.DESCRIPTION)
137                                 .add(Out.POLARITYAWARELEDDESCRIPTION)
138                                 .add(ButtonLED.DESCRIPTION)
139                                 .add(Out.SEVENDESCRIPTION)
140                                 .add(Out.SEVENHEXDESCRIPTION)
141                                 .add(Out.SIXTEENDESCRIPTION)
142                                 .add(LightBulb.DESCRIPTION)
143                                 .add(LedMatrix.DESCRIPTION)
144                         )
145                         .add(new LibraryNode(Lang.get("lib_mechanic"))
146                                 .add(RotEncoder.DESCRIPTION)
147                                 .add(StepperMotorUnipolar.DESCRIPTION)
148                                 .add(StepperMotorBipolar.DESCRIPTION)
149                         )
150                         .add(new LibraryNode(Lang.get("lib_peripherals"))
151                                 .add(Keyboard.DESCRIPTION)
152                                 .add(Terminal.DESCRIPTION)
153                                 .add(VGA.DESCRIPTION)
154                                 .add(MIDI.DESCRIPTION)
155                         )
156                 )
157                 .add(new LibraryNode(Lang.get("lib_wires"))
158                         .add(Ground.DESCRIPTION)
159                         .add(VDD.DESCRIPTION)
160                         .add(Const.DESCRIPTION)
161                         .add(Tunnel.DESCRIPTION)
162                         .add(Splitter.DESCRIPTION)
163                         .add(Driver.DESCRIPTION)
164                         .add(DriverInvSel.DESCRIPTION)
165                         .add(Delay.DESCRIPTION)
166                         .add(PullUp.DESCRIPTION)
167                         .add(PullDown.DESCRIPTION)
168                         .add(NotConnected.DESCRIPTION))
169                 .add(new LibraryNode(Lang.get("lib_mux"))
170                         .add(Multiplexer.DESCRIPTION)
171                         .add(Demultiplexer.DESCRIPTION)
172                         .add(Decoder.DESCRIPTION)
173                         .add(BitSelector.DESCRIPTION)
174                         .add(PriorityEncoder.DESCRIPTION))
175                 .add(new LibraryNode(Lang.get("lib_flipFlops"))
176                         .add(FlipflopRSAsync.DESCRIPTION)
177                         .add(FlipflopRS.DESCRIPTION)
178                         .add(FlipflopJK.DESCRIPTION)
179                         .add(FlipflopD.DESCRIPTION)
180                         .add(FlipflopT.DESCRIPTION)
181                         .add(FlipflopJKAsync.DESCRIPTION)
182                         .add(FlipflopDAsync.DESCRIPTION)
183                         .add(Monoflop.DESCRIPTION))
184                 .add(new LibraryNode(Lang.get("lib_memory"))
185                         .add(new LibraryNode(Lang.get("lib_ram"))
186                                 .add(RAMDualPort.DESCRIPTION)
187                                 .add(BlockRAMDualPort.DESCRIPTION)
188                                 .add(RAMSinglePort.DESCRIPTION)
189                                 .add(RAMSinglePortSel.DESCRIPTION)
190                                 .add(RegisterFile.DESCRIPTION)
191                                 .add(RAMDualAccess.DESCRIPTION)
192                                 .add(RAMAsync.DESCRIPTION)
193                                 .add(GraphicCard.DESCRIPTION))
194                         .add(new LibraryNode(Lang.get("lib_eeprom"))
195                                 .add(EEPROM.DESCRIPTION)
196                                 .add(EEPROMDualPort.DESCRIPTION))
197                         .add(Register.DESCRIPTION)
198                         .add(ROM.DESCRIPTION)
199                         .add(ROMDualPort.DESCRIPTION)
200                         .add(Counter.DESCRIPTION)
201                         .add(CounterPreset.DESCRIPTION)
202                         .add(PRNG.DESCRIPTION))
203                 .add(new LibraryNode(Lang.get("lib_arithmetic"))
204                         .add(Add.DESCRIPTION)
205                         .add(Sub.DESCRIPTION)
206                         .add(Mul.DESCRIPTION)
207                         .add(Div.DESCRIPTION)
208                         .add(BarrelShifter.DESCRIPTION)
209                         .add(Comparator.DESCRIPTION)
210                         .add(Neg.DESCRIPTION)
211                         .add(BitExtender.DESCRIPTION)
212                         .add(BitCount.DESCRIPTION))
213                 .add(new LibraryNode(Lang.get("lib_switching"))
214                         .add(Switch.DESCRIPTION)
215                         .add(SwitchDT.DESCRIPTION)
216                         .add(Relay.DESCRIPTION)
217                         .add(RelayDT.DESCRIPTION)
218                         .add(PFET.DESCRIPTION)
219                         .add(NFET.DESCRIPTION)
220                         .add(Fuse.DESCRIPTION)
221                         //.add(Diode.DESCRIPTION) // see class DiodeTest for further information
222                         .add(DiodeForward.DESCRIPTION)
223                         .add(DiodeBackward.DESCRIPTION)
224                         .add(FGPFET.DESCRIPTION)
225                         .add(FGNFET.DESCRIPTION)
226                         .add(TransGate.DESCRIPTION))
227                 .add(new LibraryNode(Lang.get("lib_misc"))
228                         .add(TestCaseElement.DESCRIPTION)
229                         .add(GenericInitCode.DESCRIPTION)
230                         .add(GenericCode.DESCRIPTION)
231                         .add(DummyElement.RECTDESCRIPTION)
232                         .add(PowerSupply.DESCRIPTION)
233                         .add(BusSplitter.DESCRIPTION)
234                         .add(Reset.DESCRIPTION)
235                         .add(Break.DESCRIPTION)
236                         .add(Stop.DESCRIPTION)
237                         .add(AsyncSeq.DESCRIPTION)
238                         .add(External.DESCRIPTION)
239                         .add(PinControl.DESCRIPTION));
240 
241         addExternalJarComponents(jarFile);
242 
243         custom = new ElementLibraryFolder(root, Lang.get("menu_custom"));
244 
245         File libPath = Settings.getInstance().get(Keys.SETTINGS_LIBRARY_PATH);
246         if (libPath != null && libPath.exists())
247             new ElementLibraryFolder(root, Lang.get("menu_library")).scanFolder(libPath, true);
248 
249         populateNodeMap();
250 
251         isProgrammable.clear();
252         root.traverse(libraryNode -> {
253             ElementTypeDescription d = libraryNode.getDescriptionOrNull();
254             if (d != null && d.hasAttribute(Keys.BLOWN))
255                 isProgrammable.add(d.getName());
256         });
257     }
258 
addExternalJarComponents(File file)259     void addExternalJarComponents(File file) {
260         if (file != null && file.getPath().length() > 0 && file.exists()) {
261             if (jarComponentManager == null)
262                 jarComponentManager = new JarComponentManager(this);
263             try {
264                 jarComponentManager.loadJar(file);
265             } catch (IOException | InvalidNodeException e) {
266                 exception = e;
267             }
268         }
269     }
270 
271     /**
272      * registers a component source to Digital
273      *
274      * @param source the source
275      * @return this for chained calls
276      */
registerComponentSource(ComponentSource source)277     public ElementLibrary registerComponentSource(ComponentSource source) {
278         try {
279             if (jarComponentManager == null)
280                 jarComponentManager = new JarComponentManager(this);
281             source.registerComponents(jarComponentManager);
282         } catch (InvalidNodeException e) {
283             exception = e;
284         }
285         return this;
286     }
287 
288     /**
289      * @return returns a exception during initialization or null if there was none
290      */
checkForException()291     public Exception checkForException() {
292         Exception e = exception;
293         exception = null;
294         return e;
295     }
296 
findNode(String path)297     LibraryNode findNode(String path) throws InvalidNodeException {
298         StringTokenizer st = new StringTokenizer(path, "\\/;");
299         LibraryNode node = root;
300         while (st.hasMoreTokens()) {
301             String name = st.nextToken();
302             LibraryNode found = null;
303             for (LibraryNode n : node) {
304                 if (n.getName().equals(name)) {
305                     if (n.isLeaf())
306                         throw new InvalidNodeException(Lang.get("err_Node_N_isAComponent", n));
307                     found = n;
308                 }
309             }
310             if (found == null) {
311                 found = new LibraryNode(name);
312                 node.add(found);
313             }
314             node = found;
315         }
316         return node;
317     }
318 
319     /**
320      * @return the component manager
321      */
getJarComponentManager()322     public JarComponentManager getJarComponentManager() {
323         return jarComponentManager;
324     }
325 
326     /**
327      * Returns true if element is programmable
328      *
329      * @param name the name
330      * @return true if it is programmable
331      */
isProgrammable(String name)332     public boolean isProgrammable(String name) {
333         return isProgrammable.contains(name);
334     }
335 
336     /**
337      * Sets the shape factory used to import sub circuits
338      *
339      * @param shapeFactory the shape factory
340      */
setShapeFactory(ShapeFactory shapeFactory)341     public void setShapeFactory(ShapeFactory shapeFactory) {
342         this.shapeFactory = shapeFactory;
343     }
344 
345     @Override
getShapeFactory()346     public ShapeFactory getShapeFactory() {
347         return shapeFactory;
348     }
349 
350     /**
351      * @return the node with the custom elements
352      */
getCustomNode()353     public LibraryNode getCustomNode() {
354         return custom.getNode();
355     }
356 
populateNodeMap()357     private void populateNodeMap() {
358         map.clear();
359         final PopulateMapVisitor populateMapVisitor = new PopulateMapVisitor(map);
360         root.traverse(populateMapVisitor);
361         warningMessage = populateMapVisitor.getWarningMessage();
362     }
363 
364     /**
365      * @return the warning message or null if there is none
366      */
getWarningMessage()367     public StringBuilder getWarningMessage() {
368         return warningMessage;
369     }
370 
371     /**
372      * sets the root library path
373      *
374      * @param rootLibraryPath the path
375      * @throws IOException IOException
376      */
setRootFilePath(File rootLibraryPath)377     public void setRootFilePath(File rootLibraryPath) throws IOException {
378         if (rootLibraryPath == null) {
379             if (this.rootLibraryPath != null) {
380                 this.rootLibraryPath = null;
381                 rescanFolder();
382             }
383         } else if (!rootLibraryPath.equals(this.rootLibraryPath)) {
384             this.rootLibraryPath = rootLibraryPath;
385             rescanFolder();
386         }
387     }
388 
389     /**
390      * @return the actual root file path
391      */
getRootFilePath()392     public File getRootFilePath() {
393         return rootLibraryPath;
394     }
395 
396     /**
397      * Checks if the given file is accessible from the actual library.
398      *
399      * @param file the file to check
400      * @return true if given file is importable
401      */
isFileAccessible(File file)402     public boolean isFileAccessible(File file) {
403         if (rootLibraryPath == null) return true;
404 
405         try {
406             String root = rootLibraryPath.getCanonicalPath();
407             String path = file.getParentFile().getCanonicalPath();
408             return path.startsWith(root);
409         } catch (IOException e) {
410             return false;
411         }
412     }
413 
414     /**
415      * Returns the node or null if node not present.
416      *
417      * @param elementName the name
418      * @return the node or null
419      */
getElementNodeOrNull(String elementName)420     public LibraryNode getElementNodeOrNull(String elementName) {
421         return map.get(elementName);
422     }
423 
424     @Override
getElementType(String elementName, ElementAttributes attr)425     public ElementTypeDescription getElementType(String elementName, ElementAttributes attr) throws ElementNotFoundException {
426         return getElementType(elementName);
427     }
428 
429     /**
430      * Returns a {@link ElementTypeDescription} by a given name.
431      * If not found its tried to load it.
432      *
433      * @param elementName the elements name
434      * @return the {@link ElementTypeDescription}
435      * @throws ElementNotFoundException ElementNotFoundException
436      */
getElementType(String elementName)437     public ElementTypeDescription getElementType(String elementName) throws ElementNotFoundException {
438         try {
439             LibraryNode node = map.get(elementName);
440             if (node != null)
441                 return node.getDescription();
442 
443             // effects only some old files!
444             elementName = elementName.replace("\\", "/");
445             if (elementName.contains("/")) {
446                 elementName = new File(elementName).getName();
447             }
448 
449             node = map.get(elementName);
450             if (node != null)
451                 return node.getDescription();
452 
453             if (rootLibraryPath == null)
454                 throw new ElementNotFoundException(Lang.get("err_fileNeedsToBeSaved"));
455 
456             LOGGER.debug("could not find " + elementName);
457 
458             if (System.currentTimeMillis() - lastRescanTime > MIN_RESCAN_INTERVAL) {
459                 rescanFolder();
460 
461                 node = map.get(elementName);
462                 if (node != null)
463                     return node.getDescription();
464             }
465         } catch (IOException e) {
466             throw new ElementNotFoundException(Lang.get("msg_errorImportingModel_N0", elementName), e);
467         }
468 
469         throw new ElementNotFoundException(Lang.get("err_element_N_notFound", elementName));
470     }
471 
rescanFolder()472     private void rescanFolder() {
473         LOGGER.debug("rescan folder");
474         LibraryNode cn = custom.scanFolder(rootLibraryPath, false);
475 
476         populateNodeMap();
477 
478         if (cn != null)
479             fireLibraryChanged(cn);
480         lastRescanTime = System.currentTimeMillis();
481     }
482 
483     /**
484      * Fires a library event
485      *
486      * @param node the node changed
487      */
fireLibraryChanged(LibraryNode node)488     void fireLibraryChanged(LibraryNode node) {
489         for (LibraryListener l : listeners)
490             l.libraryChanged(node);
491     }
492 
493     /**
494      * Adds a listener to this library
495      *
496      * @param listener the listener to add
497      */
addListener(LibraryListener listener)498     public void addListener(LibraryListener listener) {
499         listeners.add(listener);
500         LOGGER.debug("added library listener " + listener.getClass().getSimpleName() + ", listeners: " + listeners.size());
501     }
502 
503     /**
504      * Removes a listener from this library
505      *
506      * @param listener the listener to remove
507      */
removeListener(LibraryListener listener)508     public void removeListener(LibraryListener listener) {
509         listeners.remove(listener);
510         LOGGER.debug("removed library listener " + listener.getClass().getSimpleName() + ", listeners: " + listeners.size());
511     }
512 
513 
514     @Override
iterator()515     public Iterator<ElementContainer> iterator() {
516         ArrayList<ElementContainer> nodes = new ArrayList<>();
517         for (LibraryNode n : getRoot())
518             addToList(nodes, n, "");
519         return nodes.iterator();
520     }
521 
addToList(ArrayList<ElementContainer> nodes, LibraryNode node, String path)522     private void addToList(ArrayList<ElementContainer> nodes, LibraryNode node, String path) {
523         if (node.isLeaf()) {
524             if (node.isDescriptionLoaded()) {
525                 try {
526                     nodes.add(new ElementContainer(node.getDescription(), path));
527                 } catch (IOException e) {
528                     // can not happen because description is present!
529                 }
530             }
531         } else
532             for (LibraryNode n : node)
533                 addToList(nodes, n, concat(path, node.getName()));
534     }
535 
concat(String path, String name)536     private String concat(String path, String name) {
537         if (path.length() == 0)
538             return name;
539         return path + " - " + name;
540 
541     }
542 
543     /**
544      * Removes an element from the library to enforce a reload
545      *
546      * @param name the elements name
547      * @throws IOException IOException
548      */
invalidateElement(File name)549     public void invalidateElement(File name) throws IOException {
550         LibraryNode n = map.get(name.getName());
551         if (n != null)
552             n.invalidate();
553         else {
554             if (rootLibraryPath != null && isFileAccessible(name))
555                 rescanFolder();
556         }
557     }
558 
559     /**
560      * Updates all entries
561      *
562      * @throws IOException IOException
563      */
updateEntries()564     public void updateEntries() throws IOException {
565         rescanFolder();
566     }
567 
568     /**
569      * @return the root element
570      */
getRoot()571     public LibraryNode getRoot() {
572         return root;
573     }
574 
575     /**
576      * Imports the given file
577      *
578      * @param file the file to load
579      * @return the description
580      * @throws IOException IOException
581      */
importElement(File file)582     ElementTypeDescription importElement(File file) throws IOException {
583         try {
584             LOGGER.debug("load element " + file);
585             Circuit circuit;
586             try {
587                 circuit = Circuit.loadCircuit(file, shapeFactory);
588             } catch (FileNotFoundException e) {
589                 throw new IOException(Lang.get("err_couldNotFindIncludedFile_N0", file));
590             }
591 
592             ElementTypeDescriptionCustom description = createCustomDescription(file, circuit, this);
593             description.setShortName(createShortName(file.getName(), circuit.getAttributes().getLabel()));
594 
595             String descriptionText = Lang.evalMultilingualContent(circuit.getAttributes().get(Keys.DESCRIPTION));
596             if (descriptionText != null && descriptionText.length() > 0) {
597                 description.setDescription(descriptionText);
598             }
599             return description;
600         } catch (PinException e) {
601             throw new IOException(Lang.get("msg_errorImportingModel_N0", file), e);
602         }
603     }
604 
createShortName(String name, String userDefined)605     private String createShortName(String name, String userDefined) {
606         if (userDefined.isEmpty()) {
607             if (name.endsWith(".dig")) return name.substring(0, name.length() - 4).replace("_", "\\_");
608 
609             String transName = Lang.getNull("elem_" + name);
610             if (transName == null)
611                 return name;
612             else
613                 return transName;
614         } else {
615             return userDefined;
616         }
617     }
618 
619     /**
620      * Creates a custom element description.
621      *
622      * @param file    the file
623      * @param circuit the circuit
624      * @param library the library
625      * @return the type description
626      * @throws PinException PinException
627      */
createCustomDescription(File file, Circuit circuit, ElementLibrary library)628     public static ElementTypeDescriptionCustom createCustomDescription(File file, Circuit circuit, ElementLibrary library) throws PinException {
629         ElementTypeDescriptionCustom d = new ElementTypeDescriptionCustom(file, circuit, library);
630         d.setElementFactory(attributes -> new CustomElement(d));
631         return d;
632     }
633 
634 
635     /**
636      * Used to store a elements name and its position in the elements menu.
637      */
638     public static class ElementContainer {
639         private final ElementTypeDescription name;
640         private final String treePath;
641 
642         /**
643          * Creates anew instance
644          *
645          * @param typeDescription the elements typeDescription
646          * @param treePath        the elements menu path
647          */
ElementContainer(ElementTypeDescription typeDescription, String treePath)648         ElementContainer(ElementTypeDescription typeDescription, String treePath) {
649             this.name = typeDescription;
650             this.treePath = treePath;
651         }
652 
653         /**
654          * @return the elements name
655          */
getDescription()656         public ElementTypeDescription getDescription() {
657             return name;
658         }
659 
660         /**
661          * @return Returns the path in the menu
662          */
getTreePath()663         public String getTreePath() {
664             return treePath;
665         }
666     }
667 
668     private static final class PopulateMapVisitor implements Visitor {
669         private static final int MAX_WARNING_ENTRIES = 15;
670         private final HashMap<String, LibraryNode> map;
671         private StringBuilder warningMessage;
672         private int warningEntries = 0;
673 
PopulateMapVisitor(HashMap<String, LibraryNode> map)674         private PopulateMapVisitor(HashMap<String, LibraryNode> map) {
675             this.map = map;
676         }
677 
678         @Override
visit(LibraryNode libraryNode)679         public void visit(LibraryNode libraryNode) {
680             if (libraryNode.isLeaf()) {
681                 final String name = libraryNode.getName();
682 
683                 LibraryNode presentNode = map.get(name);
684                 if (presentNode == null) {
685                     map.put(name, libraryNode);
686                     libraryNode.setUnique(true);
687                 } else {
688                     if (presentNode.equalsFile(libraryNode))
689                         libraryNode.setUnique(true);
690                     else {
691                         presentNode.setUnique(false); // ToDo does not work if there are more than two duplicates and
692                         libraryNode.setUnique(false); // some of the duplicates point to the same file
693                         if (warningMessage == null)
694                             warningMessage = new StringBuilder(Lang.get("msg_duplicateLibraryFiles"));
695                         if (warningEntries <= MAX_WARNING_ENTRIES)
696                             warningMessage.append("\n\n").append(presentNode.getFile()).append("\n").append(libraryNode.getFile());
697                         warningEntries++;
698                     }
699                 }
700             }
701         }
702 
getWarningMessage()703         private StringBuilder getWarningMessage() {
704             if (warningEntries >= MAX_WARNING_ENTRIES) {
705                 warningMessage.append("\n\n").append(Lang.get("msg_and_N_More", warningEntries - MAX_WARNING_ENTRIES));
706                 warningEntries = 0;
707             }
708             return warningMessage;
709         }
710     }
711 }
712