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