* added number-pattern similarity metric

* improved name-matching, normalizing of names
* unit-test for new similarity metrics
* improved PreferencesList
* added EventList->List synchronizer
* included GlazedLists in build
This commit is contained in:
Reinhard Pointner 2008-06-29 17:38:57 +00:00
parent 2b4218ffce
commit d1775cf1b4
25 changed files with 676 additions and 85 deletions

View File

@ -60,15 +60,9 @@
<include name="**/*.class" /> <include name="**/*.class" />
</zipfileset> </zipfileset>
<!--
<zipfileset src="${lib.glazedlists}"> <zipfileset src="${lib.glazedlists}">
<include name="ca/odell/glazedlists/*.class" /> <include name="ca/odell/glazedlists/**" />
<include name="ca/odell/glazedlists/event/**" />
<include name="ca/odell/glazedlists/matchers/**" />
<include name="ca/odell/glazedlists/gui/**" />
<include name="ca/odell/glazedlists/swing/**" />
</zipfileset> </zipfileset>
-->
</jar> </jar>
</target> </target>

View File

@ -0,0 +1,51 @@
package net.sourceforge.filebot;
import java.util.List;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
//TODO: testcase, class doc
public class ListChangeSynchronizer<E> implements ListEventListener<E> {
private final List<E> target;
public ListChangeSynchronizer(EventList<E> source, List<E> target) {
this.target = target;
source.addListEventListener(this);
}
public void listChanged(ListEvent<E> listChanges) {
EventList<E> source = listChanges.getSourceList();
// update target list
while (listChanges.next()) {
int index = listChanges.getIndex();
int type = listChanges.getType();
switch (type) {
case ListEvent.INSERT:
target.add(index, source.get(index));
break;
case ListEvent.UPDATE:
target.set(index, source.get(index));
break;
case ListEvent.DELETE:
target.remove(index);
break;
}
}
}
public static <E> ListChangeSynchronizer<E> syncEventListToList(EventList<E> source, List<E> target) {
return new ListChangeSynchronizer<E>(source, target);
}
}

View File

@ -34,6 +34,7 @@ import net.sourceforge.tuned.ProgressIterator;
import net.sourceforge.tuned.ui.SelectButtonTextField; import net.sourceforge.tuned.ui.SelectButtonTextField;
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter; import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
import net.sourceforge.tuned.ui.TunedUtil; import net.sourceforge.tuned.ui.TunedUtil;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.swing.AutoCompleteSupport; import ca.odell.glazedlists.swing.AutoCompleteSupport;
@ -46,14 +47,13 @@ public abstract class AbstractSearchPanel<S, E, T extends JComponent> extends Fi
private final SelectButtonTextField<S> searchField; private final SelectButtonTextField<S> searchField;
private final EventList<String> searchHistory; private final EventList<String> searchHistory = new BasicEventList<String>();
public AbstractSearchPanel(String title, Icon icon, EventList<String> searchHistory) { @SuppressWarnings("unchecked")
public AbstractSearchPanel(String title, Icon icon) {
super(title, icon); super(title, icon);
this.searchHistory = searchHistory;
setLayout(new BorderLayout(10, 5)); setLayout(new BorderLayout(10, 5));
searchField = new SelectButtonTextField<S>(); searchField = new SelectButtonTextField<S>();
@ -84,6 +84,15 @@ public abstract class AbstractSearchPanel<S, E, T extends JComponent> extends Fi
add(searchBox, BorderLayout.NORTH); add(searchBox, BorderLayout.NORTH);
add(centerPanel, BorderLayout.CENTER); add(centerPanel, BorderLayout.CENTER);
/*
* TODO: fetchHistory
// no need to care about thread-safety, history-lists are only accessed from the EDT
CompositeList<Object> completionList = new CompositeList<Object>();
completionList.addMemberList((EventList) searchHistory);
completionList.addMemberList(fetchHistory);
*/
AutoCompleteSupport.install(searchField.getEditor(), searchHistory); AutoCompleteSupport.install(searchField.getEditor(), searchHistory);
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction); TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction);
@ -102,7 +111,7 @@ public abstract class AbstractSearchPanel<S, E, T extends JComponent> extends Fi
protected abstract URI getLink(S client, SearchResult searchResult); protected abstract URI getLink(S client, SearchResult searchResult);
public List<String> getSearchHistory() { public EventList<String> getSearchHistory() {
return searchHistory; return searchHistory;
} }

View File

@ -19,20 +19,18 @@ import javax.swing.SwingWorker;
import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
import net.sourceforge.filebot.ui.panel.rename.match.Match; import net.sourceforge.filebot.ui.panel.rename.matcher.Match;
import net.sourceforge.filebot.ui.panel.rename.match.Matcher; import net.sourceforge.filebot.ui.panel.rename.matcher.Matcher;
import net.sourceforge.filebot.ui.panel.rename.similarity.LengthEqualsMetric; import net.sourceforge.filebot.ui.panel.rename.metric.CompositeSimilarityMetric;
import net.sourceforge.filebot.ui.panel.rename.similarity.MultiSimilarityMetric; import net.sourceforge.filebot.ui.panel.rename.metric.NumericSimilarityMetric;
import net.sourceforge.filebot.ui.panel.rename.similarity.SimilarityMetric; import net.sourceforge.filebot.ui.panel.rename.metric.SimilarityMetric;
import net.sourceforge.filebot.ui.panel.rename.similarity.StringEqualsMetric;
import net.sourceforge.filebot.ui.panel.rename.similarity.StringSimilarityMetric;
import net.sourceforge.tuned.ui.ProgressDialog; import net.sourceforge.tuned.ui.ProgressDialog;
import net.sourceforge.tuned.ui.SwingWorkerProgressMonitor; import net.sourceforge.tuned.ui.SwingWorkerProgressMonitor;
class MatchAction extends AbstractAction { class MatchAction extends AbstractAction {
private MultiSimilarityMetric metrics; private CompositeSimilarityMetric metrics;
private final RenameList namesList; private final RenameList namesList;
private final RenameList filesList; private final RenameList filesList;
@ -50,7 +48,7 @@ class MatchAction extends AbstractAction {
this.filesList = filesList; this.filesList = filesList;
// length similarity will only effect torrent <-> file matches // length similarity will only effect torrent <-> file matches
metrics = new MultiSimilarityMetric(new StringSimilarityMetric(), new StringEqualsMetric(), new LengthEqualsMetric()); metrics = new CompositeSimilarityMetric(new NumericSimilarityMetric());
setMatchName2File(true); setMatchName2File(true);
} }
@ -69,7 +67,7 @@ class MatchAction extends AbstractAction {
} }
public MultiSimilarityMetric getMetrics() { public CompositeSimilarityMetric getMetrics() {
return metrics; return metrics;
} }
@ -92,7 +90,7 @@ class MatchAction extends AbstractAction {
ProgressDialog progressDialog = monitor.getProgressDialog(); ProgressDialog progressDialog = monitor.getProgressDialog();
progressDialog.setTitle("Matching ..."); progressDialog.setTitle("Matching ...");
progressDialog.setHeader("Matching ..."); progressDialog.setHeader(progressDialog.getTitle());
progressDialog.setIcon((Icon) getValue(SMALL_ICON)); progressDialog.setIcon((Icon) getValue(SMALL_ICON));
backgroundMatcher.execute(); backgroundMatcher.execute();

View File

@ -19,8 +19,8 @@ import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionListener;
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
import net.sourceforge.filebot.ui.panel.rename.similarity.MultiSimilarityMetric; import net.sourceforge.filebot.ui.panel.rename.metric.CompositeSimilarityMetric;
import net.sourceforge.filebot.ui.panel.rename.similarity.SimilarityMetric; import net.sourceforge.filebot.ui.panel.rename.metric.SimilarityMetric;
import net.sourceforge.tuned.ui.notification.SeparatorBorder; import net.sourceforge.tuned.ui.notification.SeparatorBorder;
@ -70,7 +70,7 @@ class SimilarityPanel extends Box {
} }
public void setMetrics(MultiSimilarityMetric metrics) { public void setMetrics(CompositeSimilarityMetric metrics) {
grid.removeAll(); grid.removeAll();
updaterList.clear(); updaterList.clear();

View File

@ -0,0 +1,38 @@
package net.sourceforge.filebot.ui.panel.rename.metric;
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
public abstract class AbstractNameSimilarityMetric implements SimilarityMetric {
@Override
public float getSimilarity(ListEntry a, ListEntry b) {
return getSimilarity(normalize(a.getName()), normalize(b.getName()));
}
protected String normalize(String name) {
name = stripChecksum(name);
name = normalizeSeparators(name);
name = name.trim();
name = name.toLowerCase();
return name;
}
protected String normalizeSeparators(String name) {
return name.replaceAll("[\\._ ]+", " ");
}
protected String stripChecksum(String name) {
return name.replaceAll("\\[\\p{XDigit}{8}\\]", "");
}
public abstract float getSimilarity(String a, String b);
}

View File

@ -0,0 +1,51 @@
package net.sourceforge.filebot.ui.panel.rename.metric;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
public class CompositeSimilarityMetric implements SimilarityMetric, Iterable<SimilarityMetric> {
private List<SimilarityMetric> similarityMetrics;
public CompositeSimilarityMetric(SimilarityMetric... metrics) {
similarityMetrics = Arrays.asList(metrics);
}
@Override
public float getSimilarity(ListEntry a, ListEntry b) {
float similarity = 0;
for (SimilarityMetric metric : similarityMetrics) {
similarity += metric.getSimilarity(a, b) / similarityMetrics.size();
}
return similarity;
}
@Override
public String getDescription() {
return null;
}
@Override
public String getName() {
return "Average";
}
@Override
public Iterator<SimilarityMetric> iterator() {
return similarityMetrics.iterator();
}
}

View File

@ -0,0 +1,36 @@
package net.sourceforge.filebot.ui.panel.rename.metric;
import net.sourceforge.filebot.ui.panel.rename.entry.AbstractFileEntry;
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
public class LengthEqualsMetric implements SimilarityMetric {
@Override
public float getSimilarity(ListEntry a, ListEntry b) {
if ((a instanceof AbstractFileEntry) && (b instanceof AbstractFileEntry)) {
long lengthA = ((AbstractFileEntry) a).getLength();
long lengthB = ((AbstractFileEntry) b).getLength();
if (lengthA == lengthB)
return 1;
}
return 0;
}
@Override
public String getDescription() {
return "Check whether file size is equal or not";
}
@Override
public String getName() {
return "Length";
}
}

View File

@ -0,0 +1,100 @@
package net.sourceforge.filebot.ui.panel.rename.metric;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import uk.ac.shef.wit.simmetrics.similaritymetrics.AbstractStringMetric;
import uk.ac.shef.wit.simmetrics.similaritymetrics.EuclideanDistance;
import uk.ac.shef.wit.simmetrics.tokenisers.InterfaceTokeniser;
import uk.ac.shef.wit.simmetrics.wordhandlers.DummyStopTermHandler;
import uk.ac.shef.wit.simmetrics.wordhandlers.InterfaceTermHandler;
public class NumericSimilarityMetric extends AbstractNameSimilarityMetric {
private final AbstractStringMetric metric;
public NumericSimilarityMetric() {
// I have absolutely no clue as to why, but I get a good matching behavior
// when using my NumberTokensier with EuclideanDistance
metric = new EuclideanDistance(new NumberTokeniser());
}
@Override
public float getSimilarity(String a, String b) {
return metric.getSimilarity(a, b);
}
@Override
public String getDescription() {
return "Similarity of number patterns";
}
@Override
public String getName() {
return "Numbers";
}
private static class NumberTokeniser implements InterfaceTokeniser {
private final String delimiter = "(\\D)+";
@Override
public ArrayList<String> tokenizeToArrayList(String input) {
ArrayList<String> tokens = new ArrayList<String>();
Scanner scanner = new Scanner(input);
scanner.useDelimiter(delimiter);
while (scanner.hasNextInt()) {
tokens.add(Integer.toString(scanner.nextInt()));
}
return tokens;
}
@Override
public Set<String> tokenizeToSet(String input) {
return new HashSet<String>(tokenizeToArrayList(input));
}
@Override
public String getShortDescriptionString() {
return getClass().getSimpleName();
}
@Override
public String getDelimiters() {
return delimiter;
}
private InterfaceTermHandler stopWordHandler = new DummyStopTermHandler();
@Override
public InterfaceTermHandler getStopWordHandler() {
return stopWordHandler;
}
@Override
public void setStopWordHandler(InterfaceTermHandler stopWordHandler) {
this.stopWordHandler = stopWordHandler;
}
}
}

View File

@ -0,0 +1,17 @@
package net.sourceforge.filebot.ui.panel.rename.metric;
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
public interface SimilarityMetric {
public float getSimilarity(ListEntry a, ListEntry b);
public String getDescription();
public String getName();
}

View File

@ -0,0 +1,41 @@
package net.sourceforge.filebot.ui.panel.rename.metric;
import uk.ac.shef.wit.simmetrics.similaritymetrics.AbstractStringMetric;
import uk.ac.shef.wit.simmetrics.similaritymetrics.MongeElkan;
import uk.ac.shef.wit.simmetrics.tokenisers.TokeniserQGram3Extended;
public class StringSimilarityMetric extends AbstractNameSimilarityMetric {
private final AbstractStringMetric metric;
public StringSimilarityMetric() {
// I have absolutely no clue as to why, but I get a good matching behavior
// when using MongeElkan with a QGram3Extended (far from perfect though)
metric = new MongeElkan(new TokeniserQGram3Extended());
//TODO QGram3Extended VS Whitespace (-> normalized values)
}
@Override
public float getSimilarity(String a, String b) {
return metric.getSimilarity(a, b);
}
@Override
public String getDescription() {
return "Similarity of names";
}
@Override
public String getName() {
return metric.getShortDescriptionString();
}
}

View File

@ -5,6 +5,7 @@ package net.sourceforge.filebot.ui.panel.subtitle;
import java.net.URI; import java.net.URI;
import java.util.List; import java.util.List;
import net.sourceforge.filebot.ListChangeSynchronizer;
import net.sourceforge.filebot.Settings; import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.ui.AbstractSearchPanel; import net.sourceforge.filebot.ui.AbstractSearchPanel;
@ -12,30 +13,28 @@ import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.web.SearchResult; import net.sourceforge.filebot.web.SearchResult;
import net.sourceforge.filebot.web.SubtitleClient; import net.sourceforge.filebot.web.SubtitleClient;
import net.sourceforge.filebot.web.SubtitleDescriptor; import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.tuned.BasicCachingList;
import net.sourceforge.tuned.FunctionIterator; import net.sourceforge.tuned.FunctionIterator;
import net.sourceforge.tuned.ProgressIterator; import net.sourceforge.tuned.ProgressIterator;
import net.sourceforge.tuned.FunctionIterator.Function; import net.sourceforge.tuned.FunctionIterator.Function;
import net.sourceforge.tuned.ui.SimpleIconProvider; import net.sourceforge.tuned.ui.SimpleIconProvider;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitlePackage, SubtitleDownloadPanel> { public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitlePackage, SubtitleDownloadPanel> {
public SubtitlePanel() { public SubtitlePanel() {
super("Subtitle", ResourceManager.getIcon("panel.subtitle"), globalSearchHistory()); super("Subtitle", ResourceManager.getIcon("panel.subtitle"));
getHistoryPanel().setColumnHeader1("Show / Movie"); getHistoryPanel().setColumnHeader1("Show / Movie");
getHistoryPanel().setColumnHeader2("Number of Subtitles"); getHistoryPanel().setColumnHeader2("Number of Subtitles");
getSearchField().getSelectButton().setModel(SubtitleClient.getAvailableSubtitleClients()); getSearchField().getSelectButton().setModel(SubtitleClient.getAvailableSubtitleClients());
getSearchField().getSelectButton().setIconProvider(SimpleIconProvider.forClass(SubtitleClient.class)); getSearchField().getSelectButton().setIconProvider(SimpleIconProvider.forClass(SubtitleClient.class));
}
List<String> persistentSearchHistory = Settings.getSettings().asStringList(Settings.SUBTITLE_HISTORY);
private static EventList<String> globalSearchHistory() { getSearchHistory().addAll(persistentSearchHistory);
return GlazedLists.eventList(new BasicCachingList<String>(Settings.getSettings().asStringList(Settings.SUBTITLE_HISTORY)));
ListChangeSynchronizer.syncEventListToList(getSearchHistory(), persistentSearchHistory);
} }

View File

@ -49,7 +49,7 @@ public class TVRageClient extends EpisodeListClient {
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size()); List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
for (Node node : nodes) { for (Node node : nodes) {
String showid = XPathUtil.selectString("showid", node); int showid = XPathUtil.selectInteger("showid", node);
String name = XPathUtil.selectString("name", node); String name = XPathUtil.selectString("name", node);
String link = XPathUtil.selectString("link", node); String link = XPathUtil.selectString("link", node);
@ -65,7 +65,7 @@ public class TVRageClient extends EpisodeListClient {
@Override @Override
public ProgressIterator<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException, ParserConfigurationException { public ProgressIterator<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException, ParserConfigurationException {
String showId = ((TVRageSearchResult) searchResult).getShowId(); int showId = ((TVRageSearchResult) searchResult).getShowId();
String episodeListUri = String.format("http://" + host + "/feeds/episode_list.php?sid=" + showId); String episodeListUri = String.format("http://" + host + "/feeds/episode_list.php?sid=" + showId);
Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(episodeListUri); Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(episodeListUri);
@ -110,18 +110,18 @@ public class TVRageClient extends EpisodeListClient {
public static class TVRageSearchResult extends SearchResult { public static class TVRageSearchResult extends SearchResult {
private final String showId; private final int showId;
private final String link; private final String link;
public TVRageSearchResult(String name, String showId, String link) { public TVRageSearchResult(String name, int showId, String link) {
super(name); super(name);
this.showId = showId; this.showId = showId;
this.link = link; this.link = link;
} }
public String getShowId() { public int getShowId() {
return showId; return showId;
} }

View File

@ -39,28 +39,36 @@ public class PreferencesList<T> extends AbstractList<T> {
@Override @Override
public boolean add(T e) { public boolean add(T e) {
prefs.put(key(size()), e); setImpl(size(), e);
return true; return true;
} }
//TODO: assert invalid index
@Override
public void add(int index, T element) {
copy(index, index + 1, size() - index);
setImpl(index, element);
}
private T setImpl(int index, T element) {
return prefs.put(key(index), element);
}
/**
* @return always null
*/
@Override @Override
public T remove(int index) { public T remove(int index) {
int lastIndex = size() - 1; int lastIndex = size() - 1;
List<T> shiftList = new ArrayList<T>(subList(index, lastIndex + 1)); copy(index + 1, index, lastIndex - index);
T value = shiftList.remove(0);
prefs.remove(key(lastIndex)); prefs.remove(key(lastIndex));
for (T element : shiftList) { return null;
set(index, element);
index++;
}
return value;
} }
@ -69,7 +77,19 @@ public class PreferencesList<T> extends AbstractList<T> {
if (index < 0 || index >= size()) if (index < 0 || index >= size())
throw new IndexOutOfBoundsException(); throw new IndexOutOfBoundsException();
return prefs.put(key(index), element); return setImpl(index, element);
}
private void copy(int startIndex, int newStartIndex, int count) {
if (count == 0 || startIndex == newStartIndex)
return;
List<T> copy = new ArrayList<T>(subList(startIndex, startIndex + count));
for (int i = newStartIndex, n = 0; n < count; i++, n++) {
setImpl(i, copy.get(n));
}
} }

View File

@ -1,6 +1,7 @@
import junit.framework.JUnit4TestAdapter; import junit.framework.JUnit4TestAdapter;
import junit.framework.Test; import junit.framework.Test;
import net.sourceforge.filebot.FileBotTestSuite;
import net.sourceforge.tuned.TunedTestSuite;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Suite; import org.junit.runners.Suite;
@ -8,7 +9,7 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class) @RunWith(Suite.class)
@SuiteClasses( { net.sourceforge.tuned.TestSuite.class, net.sourceforge.filebot.TestSuite.class }) @SuiteClasses( { FileBotTestSuite.class, TunedTestSuite.class })
public class AllTests { public class AllTests {
public static Test suite() { public static Test suite() {

View File

@ -0,0 +1,23 @@
package net.sourceforge.filebot;
import junit.framework.JUnit4TestAdapter;
import junit.framework.Test;
import net.sourceforge.filebot.ui.panel.rename.MatcherTestSuite;
import net.sourceforge.filebot.web.WebTestSuite;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses( { MatcherTestSuite.class, WebTestSuite.class })
public class FileBotTestSuite {
public static Test suite() {
return new JUnit4TestAdapter(FileBotTestSuite.class);
}
}

View File

@ -0,0 +1,23 @@
package net.sourceforge.filebot.ui.panel.rename;
import junit.framework.JUnit4TestAdapter;
import junit.framework.Test;
import net.sourceforge.filebot.ui.panel.rename.metric.AbstractNameSimilarityMetricTest;
import net.sourceforge.filebot.ui.panel.rename.metric.NumericSimilarityMetricTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses( { AbstractNameSimilarityMetricTest.class, NumericSimilarityMetricTest.class })
public class MatcherTestSuite {
public static Test suite() {
return new JUnit4TestAdapter(MatcherTestSuite.class);
}
}

View File

@ -0,0 +1,83 @@
package net.sourceforge.filebot.ui.panel.rename.metric;
import static org.junit.Assert.assertEquals;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import net.sourceforge.tuned.TestUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class AbstractNameSimilarityMetricTest {
private static final BasicNameSimilarityMetric metric = new BasicNameSimilarityMetric();
@Parameters
public static Collection<Object[]> createParameters() {
Map<String, String> matches = new LinkedHashMap<String, String>();
// normalize separators
matches.put("test s01e01 first", "test.S01E01.First");
matches.put("test s01e02 second", "test_S01E02_Second");
matches.put("test s01e03 third", "__test__S01E03__Third__");
matches.put("test s01e04 four", "test s01e04 four");
// strip checksum
matches.put("test", "test [EF62DF13]");
// lower-case
matches.put("the a-team", "The A-Team");
return TestUtil.asParameters(matches.entrySet().toArray());
}
private Entry<String, String> entry;
public AbstractNameSimilarityMetricTest(Entry<String, String> entry) {
this.entry = entry;
}
@Test
public void normalize() {
String normalizedName = entry.getKey();
String unnormalizedName = entry.getValue();
assertEquals(normalizedName, metric.normalize(unnormalizedName));
}
private static class BasicNameSimilarityMetric extends AbstractNameSimilarityMetric {
@Override
public float getSimilarity(String a, String b) {
return a.equals(b) ? 1 : 0;
}
@Override
public String getDescription() {
return "Equals";
}
@Override
public String getName() {
return "Equals";
}
}
}

View File

@ -0,0 +1,96 @@
package net.sourceforge.filebot.ui.panel.rename.metric;
import static org.junit.Assert.assertEquals;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import net.sourceforge.tuned.TestUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class NumericSimilarityMetricTest {
private static NumericSimilarityMetric metric = new NumericSimilarityMetric();
private static Map<String, String> matches = createMatches();
public static Map<String, String> createMatches() {
Map<String, String> matches = new LinkedHashMap<String, String>();
// lots of naming variations
matches.put("Test - 1x01", "test.S01E01.Pilot.HDTV.XviD-FQM");
matches.put("Test - 1x02", "test.S01E02.HDTV.XviD-DIMENSION");
matches.put("Test - 1x03", "test.S01E03.Third.time.is.the.charm.DSR.XviD-2SD");
matches.put("Test - 1x04", "test.S01E04.Four.Square.HDTV-FQM.eng");
matches.put("Test - 1x05", "test.season.1.episode.05.DSR.eng");
matches.put("Test - 1x06", "test.1x06.dsr.V1");
matches.put("Test - 1x07", "test.s01e07.dsr.tempt.12.V0");
matches.put("Test - 1x08", "test.s01e08.dsr.tempt.16.V0");
matches.put("Test - 1x09", "Test - 1x09");
matches.put("Test - 1x10", "test.s01e10.dsr.xvid-2sd.VO");
matches.put("Test - 1x11", "Test - 01x11 - The Question");
matches.put("Test - 1x12", "test.1x12.iht.VO");
matches.put("Test - 1x13", "Test.S01E13.DSR.XviD-0TV");
matches.put("Test - 1x14", "Test.S01E14.DSR.XviD-2SD");
matches.put("Test - 1x15", "Test.S01E15.DSR.XviD-0TV");
matches.put("Test - 1x16", "test.1x16.0tv.VO");
matches.put("Test - 1x17", "Test.S01E17.42.is.the.answer.DSR.XviD-0TV");
matches.put("Test - 1x18", "Test.S01E18.DSR.XviD-0TV");
// lots of numbers
matches.put("The 4400 - 1x01", "the.4400.s1e01.pilot.720p");
matches.put("The 4400 - 4x04", "the.4400.s4e04.eden.720p");
return matches;
}
@Parameters
public static Collection<Object[]> createParameters() {
return TestUtil.asParameters(matches.keySet().toArray());
}
private String normalizedName;
public NumericSimilarityMetricTest(String normalizedName) {
this.normalizedName = normalizedName;
}
@Test
public void getBestMatch() {
String match = getBestMatch(normalizedName, matches.values());
assertEquals(matches.get(normalizedName), match);
}
public String getBestMatch(String value, Collection<String> testdata) {
float maxSimilarity = -1;
String mostSimilar = null;
for (String comparisonValue : testdata) {
float similarity = metric.getSimilarity(value, comparisonValue);
// System.out.println(String.format("%s vs %s = %f", value, comparisonValue, similarity));
if (similarity > maxSimilarity) {
maxSimilarity = similarity;
mostSimilar = comparisonValue;
}
}
return mostSimilar;
}
}

View File

@ -15,7 +15,7 @@ import org.junit.Test;
public class TVRageClientTest { public class TVRageClientTest {
private TVRageClient tvrage = new TVRageClient(); private TVRageClient tvrage = new TVRageClient();
private TVRageSearchResult testResult = new TVRageSearchResult("Buffy the Vampire Slayer", "2930", "http://www.tvrage.com/Buffy_The_Vampire_Slayer"); private TVRageSearchResult testResult = new TVRageSearchResult("Buffy the Vampire Slayer", 2930, "http://www.tvrage.com/Buffy_The_Vampire_Slayer");
@Test @Test

View File

@ -1,5 +1,5 @@
package net.sourceforge.filebot; package net.sourceforge.filebot.web;
import junit.framework.JUnit4TestAdapter; import junit.framework.JUnit4TestAdapter;
@ -11,11 +11,11 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class) @RunWith(Suite.class)
@SuiteClasses( {}) @SuiteClasses( { TVRageClientTest.class })
public class TestSuite { public class WebTestSuite {
public static Test suite() { public static Test suite() {
return new JUnit4TestAdapter(TestSuite.class); return new JUnit4TestAdapter(WebTestSuite.class);
} }
} }

View File

@ -5,6 +5,7 @@ package net.sourceforge.tuned;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
@ -23,7 +24,7 @@ public class PreferencesListTest {
@BeforeClass @BeforeClass
public static void setUpBeforeClass() throws Exception { public static void setUpBeforeClass() throws Exception {
root = Preferences.userRoot().node("filebot-test/PreferencesList"); root = Preferences.userRoot().node("junit-test");
strings = root.node("strings"); strings = root.node("strings");
strings.put("0", "Rei"); strings.put("0", "Rei");
@ -59,7 +60,7 @@ public class PreferencesListTest {
@Test @Test
public void testAdd() { public void add() {
List<Integer> list = PreferencesList.map(numbers, Integer.class); List<Integer> list = PreferencesList.map(numbers, Integer.class);
list.add(3); list.add(3);
@ -70,16 +71,25 @@ public class PreferencesListTest {
@Test @Test
public void remove() { public void remove() {
temp.put("0", "Gladiator 1");
temp.put("1", "Gladiator 2");
temp.put("2", "Gladiator 3");
temp.put("3", "Gladiator 4");
List<String> list = PreferencesList.map(temp, String.class); ArrayList<String> compareValues = new ArrayList<String>();
compareValues.add("Gladiator 1");
compareValues.add("Gladiator 2");
compareValues.add("Gladiator 3");
compareValues.add("Gladiator 4");
compareValues.add("Gladiator 5");
List<String> prefs = PreferencesList.map(temp, String.class);
prefs.addAll(compareValues);
for (int index : new int[] { 4, 0, 1 }) {
prefs.remove(index);
compareValues.remove(index);
assertArrayEquals(compareValues.toArray(), prefs.toArray());
}
assertEquals("Gladiator 2", list.remove(1));
assertEquals("Gladiator 4", list.remove(2));
assertEquals("Gladiator 1", list.remove(0));
} }

View File

@ -31,7 +31,7 @@ public class PreferencesMapTest {
@BeforeClass @BeforeClass
public static void setUpBeforeClass() throws Exception { public static void setUpBeforeClass() throws Exception {
root = Preferences.userRoot().node("filebot-test/PreferencesMap"); root = Preferences.userRoot().node("junit-test");
strings = root.node("strings"); strings = root.node("strings");
strings.put("1", "Firefly"); strings.put("1", "Firefly");

View File

@ -11,16 +11,14 @@ import java.util.List;
public class TestUtil { public class TestUtil {
public static <T> List<List<T>> rotations(Collection<T> source) { public static List<Object[]> asParameters(Object... parameterSet) {
List<List<T>> rotations = new ArrayList<List<T>>(); List<Object[]> list = new ArrayList<Object[]>();
for (int i = 0; i < source.size(); i++) { for (Object parameter : parameterSet) {
List<T> copy = new ArrayList<T>(source); list.add(new Object[] { parameter });
Collections.rotate(copy, i);
rotations.add(copy);
} }
return rotations; return list;
} }
@ -35,13 +33,16 @@ public class TestUtil {
} }
public static List<Object[]> asParameters(Object... parameterSet) { public static <T> List<List<T>> rotations(Collection<T> source) {
List<Object[]> list = new ArrayList<Object[]>(); List<List<T>> rotations = new ArrayList<List<T>>();
for (Object parameter : parameterSet) { for (int i = 0; i < source.size(); i++) {
list.add(new Object[] { parameter }); List<T> copy = new ArrayList<T>(source);
Collections.rotate(copy, i);
rotations.add(copy);
} }
return list; return rotations;
} }
} }

View File

@ -12,10 +12,10 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class) @RunWith(Suite.class)
@SuiteClasses( { FunctionIteratorTest.class, PreferencesMapTest.class, PreferencesListTest.class }) @SuiteClasses( { FunctionIteratorTest.class, PreferencesMapTest.class, PreferencesListTest.class })
public class TestSuite { public class TunedTestSuite {
public static Test suite() { public static Test suite() {
return new JUnit4TestAdapter(TestSuite.class); return new JUnit4TestAdapter(TunedTestSuite.class);
} }
} }