package factOrFiction.model;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Observable;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.RAMDirectory;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import factOrFiction.Activator;
import factOrFiction.core.CardTemplate;
import factOrFiction.extension.ExtensionHandler;
import factOrFiction.extension.IExtensionCallback;
import factOrFiction.utils.CardNameUtil;
import factOrFiction.utils.FileUtils;
/**
* Manage two disparate set of card list:
* + tournament legal cards as given by external plugin
* + all registered cards from released editions as defined by plugins
* @author E0420300
*/
public class CardPool extends Observable implements ICardFinder {
private static final String FACT_OR_FICTION_EDITION_EXTENSION = "factOrFiction.core.edition";
// moniker file to identify version of index
private static final String INDEX_VERSION = "version-1.5";
// sortable indeces in Lucene
public static final String SORT_ON_EDITION = "sort-on-edition";
public static final String SORT_ON_TYPE = "sort-on-type";
public static final String SORT_ON_NAME = "sort-on-name";
// Untokenized fields in Lucene
public static final String ID = "id";
public static final String PLUGIN = "ed";
public static final String CLASS = "class";
// Searchable fields in Lucene
public static final String NAME = "name";
public static final String TYPE = "type";
public static final String TEXT = "text";
public static final String EDITION = "edition";
public static final String ARTIST = "artist";
public static final String FLAVOR = "flavor";
public static final String META = "meta";
public static final String COLOR = "color";
// Lucene needs known fixed value to do a 'find all', this is the value :)
public static final String LUCENE_DEFAULT = "qbert";
// Lucene stuff
private RAMDirectory idx = null;
private IndexSearcher idxSearcher = null;
// keeps a (normalized) name to CardTemplate mapping
// cached from queries to the registered plugins
private HashMap<String, CardTemplate> cachedMap = null;
private ExtensionHandler<CardTemplate> editionPluginHandler = null;
// we're the one
private static CardPool theInstance = new CardPool();
private CardPool() {
editionPluginHandler = new ExtensionHandler<CardTemplate>(FACT_OR_FICTION_EDITION_EXTENSION);
cachedMap = new HashMap<String, CardTemplate>(2048);
}
public static CardPool instance() {
return theInstance;
}
// access to the Lucene store
public IndexSearcher getIndexSearcher() {
return idxSearcher;
}
public boolean lookupMetaInfo(String name, String meta) {
if(idxSearcher != null) {
Query idQuery = new TermQuery(new Term(ID, CardNameUtil.getNormalizedName(name)));
Query metaQuery = new TermQuery(new Term(META, meta.toLowerCase()));
BooleanQuery query = new BooleanQuery();
query.add(idQuery, BooleanClause.Occur.MUST);
query.add(metaQuery, BooleanClause.Occur.MUST);
try {
Hits hits = idxSearcher.search(query);
return hits.length() > 0;
} catch (IOException e) {
e.getMessage();
}
}
return false;
}
public CardTemplate lookup(Document doc) {
String id = doc.getField(CardPool.ID).stringValue();
String ed = doc.getField(CardPool.PLUGIN).stringValue();
String classname = doc.getField(CardPool.CLASS).stringValue();
return lookup(ed, classname, id);
}
public CardTemplate lookup(String symbolicBundleName, String className, String name) {
String normName = CardNameUtil.getNormalizedName(name);
CardTemplate card = cachedMap.get( normName );
if(card == null) {
// check if deep search is needed
if( "".equals(className) || "".equals(symbolicBundleName))
return lookup(name);
card = editionPluginHandler.createExecutableFromBundle(symbolicBundleName, className);
if(card != null) {
CardTemplateAdapter adapter = new CardTemplateAdapter(card);
cachedMap.put(normName , adapter);
}
}
return card;
}
public CardTemplate lookup(String name) {
final String normName = CardNameUtil.getNormalizedName(name);
CardTemplate card = cachedMap.get( normName );
if(card == null) {
editionPluginHandler.scan(null, "name", new IExtensionCallback<CardTemplate> () {
public boolean created(String extension, String name, CardTemplate clazz) {
// cache data for later lookups
if(!cachedMap.containsKey(name)) {
CardTemplateAdapter adapter = new CardTemplateAdapter(clazz);
cachedMap.put(name , adapter);
}
if( normName.equals(name) )
return true;
// don't abort
return false;
}
});
}
// and try again, if found should be in the map by now
return cachedMap.get( normName );
}
public void initFromPlugins(IProgressMonitor monitor, boolean force) {
monitor.beginTask("Reading Cardpool", 6);
// check for stored Lucene index
File indexPath = Activator.getDefault().getStateLocation().append(".index").toFile();
File indexVersion = new File(indexPath, INDEX_VERSION);
System.out.println("IndexPath:" + indexPath.getAbsolutePath());
System.out.println("IndexVersion:" + indexVersion.getAbsolutePath());
if( force || !indexPath.exists() || !indexVersion.exists() ) {
// create path
indexPath.mkdirs();
// clear out directory
FileUtils.deleteDirectoryContents(indexPath);
try {
Directory tempIdx = FSDirectory.getDirectory(indexPath, true);
// gather all MetaInfo Provider
ExtensionHandler<IMetaEnhancer> tournamentFormatPluginhandler = new ExtensionHandler<IMetaEnhancer>("factOrFiction.tournamentformat");
final List<IMetaEnhancer> enhancer = tournamentFormatPluginhandler.createExecutables();
if(enhancer != null) {
for (IMetaEnhancer metaEnhancer : enhancer) {
metaEnhancer.restore();
}
}
// Make an writer to create the index
final IndexWriter writer = new IndexWriter(tempIdx, new StandardAnalyzer(), true);
SubProgressMonitor sub = new SubProgressMonitor(monitor, 3);
editionPluginHandler.scan(sub, NAME, new IExtensionCallback<CardTemplate>() {
public boolean created(String plugin, String name, CardTemplate clazz) {
try {
String classname = clazz.getClass().getName();
Document doc = new Document();
// Add the title as an unindexed field...
// This way we can retrieve it from the HashMap.
// I would like to store something else than this string reference
// but Lucene only allows for strings to be stored!?!?
doc.add( new Field(ID, name, Field.Store.YES, Field.Index.UN_TOKENIZED) );
doc.add( new Field(PLUGIN, plugin, Field.Store.YES, Field.Index.UN_TOKENIZED) );
doc.add( new Field(CLASS, classname, Field.Store.YES, Field.Index.UN_TOKENIZED) );
// ...and the content as an indexed field. Note that indexed
doc.add(new Field(NAME, clazz.getName(), Field.Store.NO, Field.Index.TOKENIZED));
doc.add(new Field(TEXT, clazz.getText(), Field.Store.NO, Field.Index.TOKENIZED));
doc.add(new Field(TYPE, clazz.getType(), Field.Store.NO, Field.Index.TOKENIZED));
doc.add(new Field(FLAVOR, clazz.getFlavor(), Field.Store.NO, Field.Index.TOKENIZED));
doc.add(new Field(ARTIST, clazz.getArtist(), Field.Store.NO, Field.Index.TOKENIZED));
doc.add(new Field(EDITION, clazz.getEdition(), Field.Store.NO, Field.Index.TOKENIZED));
// build meta info, add known key
StringBuffer meta = new StringBuffer(LUCENE_DEFAULT);
for (IMetaEnhancer metaEnhancer : enhancer) {
String addOnMeta = metaEnhancer.getMetaInfo(clazz.getName());
if(addOnMeta != null) {
meta.append(' ');
meta.append( addOnMeta );
}
}
// Meta info such as tournament legality for filtering, searching
doc.add( new Field(META, meta.toString(), Field.Store.NO, Field.Index.TOKENIZED) );
// TODO add direct info on color, hybrid
// build meta info, add known key
StringBuffer color = new StringBuffer();
if(clazz.isWhite())
color.append(" white");
if(clazz.isBlue())
color.append(" blue");
if(clazz.isBlack())
color.append(" black");
if(clazz.isRed())
color.append(" red");
if(clazz.isGreen())
color.append(" green");
if(clazz.isHybrid())
color.append(" hybrid");
if(clazz.isLand())
color.append(" land");
doc.add( new Field(COLOR, color.toString(), Field.Store.NO, Field.Index.TOKENIZED) );
// Sortable index on the title field
doc.add(new Field(SORT_ON_NAME, clazz.getName(), Field.Store.YES, Field.Index.UN_TOKENIZED));
doc.add(new Field(SORT_ON_TYPE, clazz.getType(), Field.Store.YES, Field.Index.UN_TOKENIZED));
doc.add(new Field(SORT_ON_EDITION, clazz.getEdition(), Field.Store.YES, Field.Index.UN_TOKENIZED));
writer.addDocument(doc);
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
});
// clean-up created index
writer.optimize();
writer.close();
tempIdx.close();
// create index version moniker
indexVersion.createNewFile();
monitor.worked(1);
} catch (IOException e) {
e.printStackTrace();
}
}
try {
// initialize our Lucene in memory storage (could be from existing index!)
idx = new RAMDirectory(indexPath);
monitor.worked(1);
// setup an index searcher
idxSearcher = new IndexSearcher(idx);
monitor.worked(1);
// notify everybody about changes
setChanged();
notifyObservers(idxSearcher);
} catch (IOException e) {
e.printStackTrace();
}
}
}