package ch.uzh.ifi.attempto.acewiki.gf;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ch.uzh.ifi.attempto.acewiki.core.ModuleElement;
import ch.uzh.ifi.attempto.acewiki.core.Ontology;
import ch.uzh.ifi.attempto.acewiki.core.OntologyElement;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import nextapp.echo.app.table.AbstractTableModel;
import nextapp.echo.app.table.TableModel;
/**
* <p>Represents a set of the GF modules as a table where rows are functions, columns are the modules,
* and each cell contains the linearization definition of the given function in the given module.
* The table is compiled only from the source files.</p>
*
* <p>Note that we only consider modules whose source has a certain simple structure which
* we can easily parse with a regex. The module must be a concrete module with a single "lin"
* on its own line, separating the "header" of the module from the linearization definitions.</p>
*
* @author Kaarel Kaljurand
*/
public class GfLexiconEditorModel extends AbstractTableModel implements TableModel {
private static final long serialVersionUID = -2494830121762821312L;
// TODO: using ; in the linearization definition is not supported
private static final Pattern PATTERN_LIN = Pattern.compile("\\s*([A-Za-z_][A-Za-z0-9_']*)\\s*=([^;]+);");
private static final Pattern PATTERN_SPLIT = Pattern.compile("^(.*concrete.+of.+\\nlin\\n)(.*)}\\s*$", Pattern.DOTALL);
private final Table<String, ModuleElement, String> funToModuleToLin = HashBasedTable.create();
private final List<String> mFuns;
private final List<ModuleElement> mModules;
private final Map<ModuleElement, String> mModuleToHeader = Maps.newHashMap();
public GfLexiconEditorModel(Ontology ont, final String language) {
OntologyElement oe = ont.getElement(language);
final ModuleElement languageModule;
if (oe != null && oe instanceof ModuleElement) {
languageModule = (ModuleElement) oe;
} else {
languageModule = null;
}
for (ModuleElement gfModule : ont.getOntologyElements(ModuleElement.class)) {
refreshModuleElement(gfModule);
}
mFuns = Lists.newArrayList(funToModuleToLin.rowKeySet());
mModules = Lists.newArrayList(funToModuleToLin.columnKeySet());
Collections.sort(mFuns, String.CASE_INSENSITIVE_ORDER);
/*
* We sort the columns so that the module for the selected language comes first.
* If no module corresponds to the selected language then prefer modules that
* the selected language module references. This handles incomplete modules
* which are referenced by concrete modules.
*/
Collections.sort(mModules, new Comparator<ModuleElement>() {
@Override
public int compare(ModuleElement arg1, ModuleElement arg2) {
if (languageModule.equals(arg1)) {
return -1;
}
if (languageModule.equals(arg2)) {
return 1;
}
if (languageModule.references(arg1)) {
return -1;
}
if (languageModule.references(arg2)) {
return 1;
}
String moduleName1 = arg1.getWord();
String moduleName2 = arg2.getWord();
return moduleName1.compareTo(moduleName2);
}
});
}
@Override
public int getColumnCount() {
return 1 + mModules.size();
}
@Override
public int getRowCount() {
return mFuns.size();
}
@Override
public Object getValueAt(int column, int row) {
String fun = mFuns.get(row);
if (column == 0) {
return fun;
}
return funToModuleToLin.get(fun, getModuleElement(column));
}
@Override
public String getColumnName(int column) {
if (column == 0) {
return null;
}
return getModuleElement(column).getWord();
}
public void setValueAt(Object value, int column, int row) {
ModuleElement moduleElement = getModuleElement(column);
refreshModuleElement(moduleElement);
String header = mModuleToHeader.get(moduleElement);
if (header != null) {
if (value == null || value.toString().trim().isEmpty()) {
funToModuleToLin.remove(mFuns.get(row), moduleElement);
} else {
funToModuleToLin.put(mFuns.get(row), moduleElement, value.toString().trim());
}
moduleElement.replaceModuleContent(header + makeModuleSource(moduleElement) + "}");
}
fireTableCellUpdated(column, row);
}
/**
* @param column table column
* @return module element that corresponds to the given column
*/
public ModuleElement getModuleElement(int column) {
if (column > 0 && column <= mModules.size()) {
return mModules.get(column - 1);
} else {
throw new IllegalArgumentException();
}
}
public List<ModuleElement> getModules() {
return ImmutableList.copyOf(mModules);
}
private boolean refreshModuleElement(ModuleElement gfModule) {
String content = gfModule.getModuleContent().getText();
Matcher matcherSplit = PATTERN_SPLIT.matcher(content);
if (! matcherSplit.matches()) {
mModuleToHeader.remove(gfModule);
return false;
}
mModuleToHeader.put(gfModule, matcherSplit.group(1));
Matcher matcherLins = PATTERN_LIN.matcher(matcherSplit.group(2));
while (matcherLins.find()) {
String fun = matcherLins.group(1);
String lin = matcherLins.group(2).trim();
funToModuleToLin.put(fun, gfModule, lin);
}
return true;
}
/**
* <p>For the given module, return its linearization definitions.
* If a definition is missing for a function then the respective entry is not included.</p>
*/
private StringBuilder makeModuleSource(ModuleElement module) {
StringBuilder sb = new StringBuilder();
for (String fun : mFuns) {
Object value = funToModuleToLin.get(fun, module);
if (value != null && ! "".equals(value.toString())) {
sb.append(fun);
sb.append(" = ");
sb.append(value);
sb.append(" ;\n");
}
}
return sb;
}
}