package com.tyndalehouse.step.tools.analysis; import com.tyndalehouse.step.core.utils.StringUtils; import org.apache.lucene.index.Term; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.FSDirectory; import org.crosswire.jsword.book.*; import org.crosswire.jsword.passage.Key; import org.crosswire.jsword.passage.Verse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.*; /** * @author chrisburrell */ public class ChiasticStructure { private static Logger LOGGER = LoggerFactory.getLogger(ChiasticStructure.class); private Map<String, String> entries = new HashMap<String, String>(256); private String ref; private class KeyValuePair<T, S> { private T key; private S value; public KeyValuePair(final T key, final S value) { this.key = key; this.value = value; } } private class Match { private Key source; private Key target; private int distance; private int commonality; private Key currentMin; private Key currentMax; } public ChiasticStructure(final String ref) { this.ref = ref; } private void appendLexicalEntry(StringBuilder stringResults, final IndexSearcher indexSearcher, String strong) throws IOException { if (strong.length() > 5 && strong.charAt(1) == '0') { strong = strong.substring(0, 1) + strong.substring(2); } String gloss = entries.get(strong); if (gloss == null) { final TopDocs lexicalEntries = indexSearcher.search(new TermQuery(new Term("strongNumber", strong)), Integer.MAX_VALUE); if (lexicalEntries.scoreDocs.length > 0) { gloss = indexSearcher.doc(lexicalEntries.scoreDocs[0].doc).get("stepGloss"); } else { gloss = ""; } entries.put(strong, gloss); } stringResults.append(strong); stringResults.append(":"); stringResults.append(gloss); stringResults.append(" "); } private void search() throws Exception { final File path = new File("C:\\Users\\Chris\\AppData\\Roaming\\JSword\\step\\entities\\definition"); FSDirectory directory = FSDirectory.open(path); final IndexSearcher indexSearcher = new IndexSearcher(directory); Book esv = Books.installed().getBook("ESV-THE"); final Key key = esv.getKey(ref); final Iterator<Key> iterator = key.iterator(); List<KeyValuePair<Key, Set<String>>> strongsInVerses = new ArrayList<KeyValuePair<Key, Set<String>>>(); TreeMap<Key, List<Match>> matchesByVerse = new TreeMap<Key, List<Match>>(); while (iterator.hasNext()) { final Key singleVerse = iterator.next(); if (createStrongData(esv, key, strongsInVerses, singleVerse)) { matchesByVerse.put(singleVerse, findPreviouslyRelatedVerses(strongsInVerses, indexSearcher)); } } findChiasms(matchesByVerse); } private void findChiasms(final TreeMap<Key, List<Match>> matchesByVerse) { //look for a set of matches that has a distance of 1 int baseDistance = 1; for (Map.Entry<Key, List<Match>> verse : matchesByVerse.entrySet()) { LOGGER.debug("Processing verse {}", verse.getKey().getName()); findMoreChiasticVerses(new Stack<Match>(), baseDistance, matchesByVerse, null, false); } } private void findMoreChiasticVerses(final Stack<Match> matchesSoFar, int distance, final TreeMap<Key, List<Match>> matchesByVerse, Match currentMatch, boolean lastChance) { List<Match> chiasticVerses = detectChiasm(distance, matchesByVerse, currentMatch, lastChance); if (chiasticVerses.size() != 0) { // LOGGER.debug("Recursive matches have been found for distance {} in verses: {}.", distance, verseKey); StringBuilder tabs = new StringBuilder(); for (int ii = 0; ii < matchesSoFar.size(); ii++) { tabs.append('\t'); } //then go looking for the next door neighbours for (Match chiasticVerse : chiasticVerses) { matchesSoFar.push(chiasticVerse); LOGGER.trace("{}between verses {} and {}", tabs, chiasticVerse.source, chiasticVerse.target); //not sure this line is correct either. findMoreChiasticVerses(matchesSoFar, chiasticVerse.distance, matchesByVerse, chiasticVerse, false); log(matchesSoFar); matchesSoFar.pop(); } } else if (!lastChance) { //need to pass in something for parameter 4 findMoreChiasticVerses(matchesSoFar, distance, matchesByVerse, currentMatch, true); } } private void log(final Stack<Match> matchesSoFar) { StringBuilder sb = new StringBuilder(); if(matchesSoFar.size() > 2) { for(Match m : matchesSoFar) { sb.append('('); sb.append(m.source.getName()); sb.append('-'); sb.append(m.target.getName()); sb.append(") "); } LOGGER.debug(sb.toString()); } } private List<Match> detectChiasm(final int currentDistance, final Map<Key, List<Match>> matches, Match currentMatch, boolean lastChance) { List<Match> potentialChiasticVerses = new ArrayList<Match>(); //look for matches at a particular distance for (List<Match> matchByVerse : matches.values()) { for (Match match : matchByVerse) { if(match == currentMatch) { continue; } //if we're between 1 or 2 of the sought distance then return; boolean foundMatch = currentMatch == null && currentDistance - match.distance >= -2; if (currentMatch != null && !foundMatch && match.distance >= currentDistance) { final int sourceDistance = ((Verse) currentMatch.source).getOrdinal() - ((Verse) match.source).getOrdinal(); final int targetDistance = ((Verse) match.target).getOrdinal() - ((Verse) currentMatch.target).getOrdinal(); int extraAllowed = lastChance ? 2 : 0; foundMatch = sourceDistance >= 0 && sourceDistance <= 2 + extraAllowed && targetDistance >= 0 && targetDistance <= 2 + extraAllowed; } if (foundMatch) { potentialChiasticVerses.add(match); LOGGER.trace("Found {} potential chiastic verses.", potentialChiasticVerses.size()); } } } return potentialChiasticVerses; } /** * Looks back from the end of the array trying to match based on the fact it matches similar strong numbers * * @param strongsInVerses */ private List<Match> findPreviouslyRelatedVerses(final List<KeyValuePair<Key, Set<String>>> strongsInVerses, IndexSearcher searcher) throws IOException { List<Match> matches = new ArrayList<Match>(64); //take the last verse and try and match it final KeyValuePair<Key, Set<String>> lastVerse = strongsInVerses.get(strongsInVerses.size() - 1); for (int ii = strongsInVerses.size() - 2; ii >= 0; ii--) { final KeyValuePair<Key, Set<String>> previousVerse = strongsInVerses.get(ii); StringBuilder matchesString = new StringBuilder(); final Match match = matches(strongsInVerses.size() - ii, lastVerse, previousVerse, matchesString, searcher); if (match != null) { LOGGER.debug("{} matches verse {} on {} counts at distance {}: {}", lastVerse.key.getName(), previousVerse.key.getName(), match.commonality, match.distance, matchesString); matches.add(match); } } return matches; } private Match matches(final int distance, final KeyValuePair<Key, Set<String>> lastVerse, final KeyValuePair<Key, Set<String>> previousVerse, StringBuilder matchesString, IndexSearcher searcher) throws IOException { Set<String> previousStrongs = previousVerse.value; Set<String> lastStrongs = lastVerse.value; int count = 0; for (String s : lastStrongs) { if (previousStrongs.contains(s)) { this.appendLexicalEntry(matchesString, searcher, s); count++; } } if (count > 1) { //assume a match Match m = new Match(); m.source = previousVerse.key; m.target = lastVerse.key; m.distance = distance; m.commonality = count; return m; } return null; } private boolean createStrongData(final Book esv, final Key key, final List<KeyValuePair<Key, Set<String>>> strongsInVerses, final Key singleVerse) throws BookException { BookData data = new BookData(esv, singleVerse); String strongs = OSISUtil.getStrongsNumbers(data.getOsisFragment()); if (StringUtils.isBlank(strongs)) { //a key without strongs return false; } String[] strongsArray = strongs.split(" "); Set<String> strongsSet = new HashSet<String>(Arrays.asList(strongsArray)); //add set of strongs to the hash map of verse to strongs strongsInVerses.add(new KeyValuePair<Key, Set<String>>(singleVerse, strongsSet)); return true; } public static void main(String[] args) throws Exception { new ChiasticStructure("Gen.3").search(); } }