// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.check; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadPoolExecutor; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.ProgressMonitor; import org.infinity.NearInfinity; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.SpawnResourceRef; import org.infinity.gui.Center; import org.infinity.gui.ChildFrame; import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; import org.infinity.resource.Resource; import org.infinity.resource.ResourceFactory; import org.infinity.resource.StructEntry; import org.infinity.resource.cre.CreResource; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.text.PlainTextResource; import org.infinity.search.ReferenceHitFrame; import org.infinity.util.Debugging; import org.infinity.util.Misc; public final class ResRefChecker extends ChildFrame implements ActionListener, Runnable { private static final String FMT_PROGRESS = "Checking %ss..."; private static final String[] FILETYPES = {"ARE", "CHR", "CHU", "CRE", "DLG", "EFF", "GAM", "ITM", "PRO", "SPL", "STO", "VEF", "VVC", "WED", "WMP"}; private final JButton bstart = new JButton("Check", Icons.getIcon(Icons.ICON_FIND_16)); private final JButton bcancel = new JButton("Cancel", Icons.getIcon(Icons.ICON_DELETE_16)); private final JButton binvert = new JButton("Invert", Icons.getIcon(Icons.ICON_REFRESH_16)); private final JCheckBox[] boxes = new JCheckBox[FILETYPES.length]; private final ReferenceHitFrame hitFrame; private List<ResourceEntry> files; private List<String> extraValues; private ProgressMonitor progress; private int progressIndex; public ResRefChecker() { super("ResRef Checker"); setIconImage(Icons.getIcon(Icons.ICON_REFRESH_16).getImage()); hitFrame = new ReferenceHitFrame("Illegal ResourceRefs", NearInfinity.getInstance()); ResourceEntry spawnRef = ResourceFactory.getResourceEntry("SPAWNGRP.2DA"); if (spawnRef != null) { PlainTextResource spawn = (PlainTextResource)ResourceFactory.getResource(spawnRef); extraValues = spawn.extract2DAHeaders(); } bstart.setMnemonic('s'); bcancel.setMnemonic('c'); binvert.setMnemonic('i'); bstart.addActionListener(this); bcancel.addActionListener(this); binvert.addActionListener(this); getRootPane().setDefaultButton(bstart); JPanel boxpanel = new JPanel(new GridLayout(0, 2, 3, 3)); for (int i = 0; i < boxes.length; i++) { boxes[i] = new JCheckBox(FILETYPES[i], true); boxpanel.add(boxes[i]); } boxpanel.setBorder(BorderFactory.createEmptyBorder(3, 12, 3, 0)); JPanel ipanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); ipanel.add(binvert); JPanel innerpanel = new JPanel(new BorderLayout()); innerpanel.add(boxpanel, BorderLayout.CENTER); innerpanel.add(ipanel, BorderLayout.SOUTH); innerpanel.setBorder(BorderFactory.createTitledBorder("Select files to check:")); JPanel bpanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); bpanel.add(bstart); bpanel.add(bcancel); JPanel mainpanel = new JPanel(new BorderLayout()); mainpanel.add(innerpanel, BorderLayout.CENTER); mainpanel.add(bpanel, BorderLayout.SOUTH); mainpanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); JPanel pane = (JPanel)getContentPane(); pane.setLayout(new BorderLayout()); pane.add(mainpanel, BorderLayout.CENTER); pack(); Center.center(this, NearInfinity.getInstance().getBounds()); setVisible(true); } // --------------------- Begin Interface ActionListener --------------------- @Override public void actionPerformed(ActionEvent event) { if (event.getSource() == bstart) { setVisible(false); files = new ArrayList<ResourceEntry>(); for (int i = 0; i < FILETYPES.length; i++) { if (boxes[i].isSelected()) files.addAll(ResourceFactory.getResources(FILETYPES[i])); } if (files.size() > 0) new Thread(this).start(); } else if (event.getSource() == binvert) { for (final JCheckBox box : boxes) box.setSelected(!box.isSelected()); } else if (event.getSource() == bcancel) setVisible(false); } // --------------------- End Interface ActionListener --------------------- // --------------------- Begin Interface Runnable --------------------- @Override public void run() { try { String type = "WWWW"; progressIndex = 0; progress = new ProgressMonitor(NearInfinity.getInstance(), "Checking...", String.format(FMT_PROGRESS, type), 0, files.size()); progress.setMillisToDecideToPopup(100); ThreadPoolExecutor executor = Misc.createThreadPool(); boolean isCancelled = false; Debugging.timerReset(); for (int i = 0; i < files.size(); i++) { ResourceEntry entry = files.get(i); if (i % 10 == 0) { String ext = entry.getExtension(); if (ext != null && !type.equalsIgnoreCase(ext)) { type = ext; progress.setNote(String.format(FMT_PROGRESS, type)); } } Misc.isQueueReady(executor, true, -1); executor.execute(new Worker(entry)); if (progress.isCanceled()) { isCancelled = true; break; } } // enforcing thread termination if process has been cancelled if (isCancelled) { executor.shutdownNow(); } else { executor.shutdown(); } // waiting for pending threads to terminate while (!executor.isTerminated()) { if (!isCancelled && progress.isCanceled()) { executor.shutdownNow(); isCancelled = true; } try { Thread.sleep(1); } catch (InterruptedException e) {} } if (isCancelled) { hitFrame.close(); JOptionPane.showMessageDialog(NearInfinity.getInstance(), "Check cancelled", "Info", JOptionPane.INFORMATION_MESSAGE); } else { hitFrame.setVisible(true); } } finally { advanceProgress(true); } Debugging.timerShow("Check completed", Debugging.TimeFormat.MILLISECONDS); } // --------------------- End Interface Runnable --------------------- private void search(ResourceEntry entry, AbstractStruct struct) { List<StructEntry> structList = struct.getFlatList(); for (int i = 0; i < structList.size(); i++) { Object o = structList.get(i); if (o instanceof SpawnResourceRef) { SpawnResourceRef ref = (SpawnResourceRef)o; String resourceName = ref.getResourceName(); if (resourceName.equalsIgnoreCase("None")) { // ignore } else if (extraValues != null && extraValues.contains(ref.getText())) { // ignore } else if (!ResourceFactory.resourceExists(resourceName)) { synchronized (hitFrame) { hitFrame.addHit(entry, entry.getSearchString(), ref); } } else if (!ref.isLegalEntry(ResourceFactory.getResourceEntry(resourceName))) { synchronized (hitFrame) { hitFrame.addHit(entry, entry.getSearchString(), ref); } } } else if (o instanceof ResourceRef) { ResourceRef ref = (ResourceRef)o; String resourceName = ref.getResourceName(); if (resourceName.equalsIgnoreCase("None")) { // ignore } else if (struct instanceof CreResource && resourceName.substring(0, 3).equalsIgnoreCase("rnd")) { // ignore } else if (!ResourceFactory.resourceExists(resourceName)) { synchronized (hitFrame) { hitFrame.addHit(entry, entry.getSearchString(), ref); } } else if (!ref.isLegalEntry(ResourceFactory.getResourceEntry(resourceName))) { synchronized (hitFrame) { hitFrame.addHit(entry, entry.getSearchString(), ref); } } } } } private synchronized void advanceProgress(boolean finished) { if (progress != null) { if (finished) { progressIndex = 0; progress.close(); progress = null; } else { progressIndex++; progress.setProgress(progressIndex); } } } //-------------------------- INNER CLASSES -------------------------- private class Worker implements Runnable { private final ResourceEntry entry; public Worker(ResourceEntry entry) { this.entry = entry; } @Override public void run() { if (entry != null) { Resource resource = ResourceFactory.getResource(entry); if (resource != null) { search(entry, (AbstractStruct)resource); } } advanceProgress(false); } } }