/* * Copyright 2013 Corpuslinguistic working group Humboldt University Berlin. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package annis.gui.flatquerybuilder; import annis.gui.QueryController; import annis.gui.objects.Query; import annis.libgui.Helper; import annis.service.objects.AnnisAttribute; import com.sun.jersey.api.client.ClientHandlerException; import com.sun.jersey.api.client.GenericType; import com.sun.jersey.api.client.UniformInterfaceException; import com.sun.jersey.api.client.WebResource; import com.vaadin.data.Property; import com.vaadin.ui.Alignment; import com.vaadin.ui.Button; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.MenuBar; import com.vaadin.ui.NativeSelect; import com.vaadin.ui.Notification; import com.vaadin.ui.Panel; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.themes.ChameleonTheme; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /* * @author martin klotz (martin.klotz@hu-berlin.de) * @author tom ruette (tom.ruette@hu-berlin.de) */ public class FlatQueryBuilder extends Panel implements Button.ClickListener, Property.ValueChangeListener { private static final Logger log = LoggerFactory.getLogger(FlatQueryBuilder.class); private Button btGo; private Button btClear; private Button btInverse; private Button btInitSpan; private Button btInitMeta; private Button btInitLanguage; private MenuBar addMenu; private MenuBar addMenuSpan; private MenuBar addMenuMeta; private QueryController cp; private HorizontalLayout language; private HorizontalLayout languagenodes; private HorizontalLayout span; private HorizontalLayout meta; private HorizontalLayout toolbar; private HorizontalLayout advanced; private VerticalLayout mainLayout; private NativeSelect filtering; private Collection<VerticalNode> vnodes; private Collection<EdgeBox> eboxes; private Collection<MetaBox> mboxes; private SpanBox spbox; private String query; private MenuBar.MenuItem spanMenu; private ReducingStringComparator rsc; private static final String[] REGEX_CHARACTERS = {"\\", "+", ".", "[", "*", "^","$", "|", "?", "(", ")"}; private static final String BUTTON_GO_LABEL = "Create AQL Query"; private static final String BUTTON_CLEAR_LABEL = "Clear the Query Builder"; private static final String BUTTON_INV_LABEL = "Refresh Query Builder"; private static final String NO_CORPORA_WARNING = "No corpora selected, please select " + "at least one corpus."; private static final String INCOMPLETE_QUERY_WARNING = "Query seems to be incomplete."; private static final String QUERY_ERROR_WARNING = "An Error occured. Please check your query."; private static final String ADD_LING_PARAM = "Add"; private static final String ADD_SPAN_PARAM = "Add"; private static final String CHANGE_SPAN_PARAM = "Change"; private static final String ADD_META_PARAM = "Add"; private static final String INFO_INIT_LANG = "In this part of the Query Builder, " + "blocks of the linguistic query can be constructed from left to right."; private static final String INFO_INIT_SPAN = "This part of the Query Builder " + "allows you to define a span annotation within which the above query blocks " + "are confined."; private static final String INFO_INIT_META = "Here, you can constrain the linguistic " + "query by selecting meta levels."; private static final String INFO_FILTER = "When searching in the fields, the " + "hits are sorted and filtered according to different mechanisms. Please " + "choose a filtering mechanism here."; private static final String TOOLBAR_CAPTION = "Toolbar"; private static final String META_CAPTION = "Meta information"; private static final String SPAN_CAPTION = "Scope"; private static final String LANG_CAPTION = "Linguistic sequence"; private static final String ADVANCED_CAPTION = "Advanced settings"; public FlatQueryBuilder(QueryController cp) { setSizeFull(); launch(cp); } private void launch(QueryController cp) { this.cp = cp; rsc = new ReducingStringComparator(); this.query = ""; mainLayout = new VerticalLayout(); // tracking lists for vertical nodes, edgeboxes and metaboxes vnodes = new ArrayList<>(); eboxes = new ArrayList<>(); mboxes = new ArrayList<>(); spbox = null; // buttons and checks btGo = new Button(BUTTON_GO_LABEL, (Button.ClickListener) this); btGo.setStyleName(ChameleonTheme.BUTTON_SMALL); btClear = new Button(BUTTON_CLEAR_LABEL, (Button.ClickListener) this); btClear.setStyleName(ChameleonTheme.BUTTON_SMALL); btInverse = new Button(BUTTON_INV_LABEL, (Button.ClickListener) this); btInverse.setStyleName(ChameleonTheme.BUTTON_SMALL); btInitLanguage = new Button("Initialize", (Button.ClickListener) this); btInitLanguage.setDescription(INFO_INIT_LANG); btInitSpan = new Button("Initialize", (Button.ClickListener) this); btInitSpan.setDescription(INFO_INIT_SPAN); btInitMeta = new Button("Initialize", (Button.ClickListener) this); btInitMeta.setDescription(INFO_INIT_META); filtering = new NativeSelect("Filtering mechanisms"); filtering.setDescription(INFO_FILTER); ReducingStringComparator rdc = new ReducingStringComparator(); Set mappings = rdc.getMappings().keySet(); int i; for (i=0; i<mappings.size(); i++){ String mapname = (String) mappings.toArray()[i]; filtering.addItem(i); filtering.setItemCaption(i, mapname); } filtering.addItem(i+1); filtering.setItemCaption(i+1, "generic"); filtering.select(i+1); filtering.setNullSelectionAllowed(false); filtering.setImmediate(true); // language layout language = new HorizontalLayout(); languagenodes = new HorizontalLayout(); language.addComponent(languagenodes); language.addComponent(btInitLanguage); language.setMargin(true); language.setCaption(LANG_CAPTION); language.addStyleName("linguistics-panel"); // span layout span = new HorizontalLayout(); span.setSpacing(true); span.addComponent(btInitSpan); span.setMargin(true); span.setCaption(SPAN_CAPTION); span.addStyleName("span-panel"); // meta layout meta = new HorizontalLayout(); meta.setSpacing(true); meta.addComponent(btInitMeta); meta.setMargin(true); meta.setCaption(META_CAPTION); meta.addStyleName("meta-panel"); // toolbar layout toolbar = new HorizontalLayout(); toolbar.setSpacing(true); toolbar.addComponent(btGo); toolbar.addComponent(btClear); toolbar.addComponent(btInverse); toolbar.setMargin(true); toolbar.setCaption(TOOLBAR_CAPTION); toolbar.addStyleName("toolbar-panel"); // advanced advanced = new HorizontalLayout(); advanced.setSpacing(true); advanced.addComponent(filtering); advanced.setMargin(true); advanced.setCaption(ADVANCED_CAPTION); advanced.addStyleName("advanced-panel"); // put everything on the layout mainLayout.setSpacing(true); mainLayout.addComponent(language); mainLayout.addComponent(span); mainLayout.addComponent(meta); mainLayout.addComponent(toolbar); mainLayout.addComponent(advanced); setContent(mainLayout); getContent().setWidth("100%"); getContent().setHeight("-1px"); } @Override public void valueChange(Property.ValueChangeEvent event) { initialize(); } @Override public void attach() { super.attach(); cp.getState().getSelectedCorpora().addValueChangeListener(this); } @Override public void detach() { super.detach(); cp.getState().getSelectedCorpora().removeValueChangeListener(this); } private void initialize() { // try to remove all existing menus try { language.removeComponent(addMenu); span.removeComponent(addMenuSpan); meta.removeComponent(addMenuMeta); } catch (Exception e) { log.error(null, e); } //init variables: final FlatQueryBuilder sq = this; Collection<String> annonames = getAvailableAnnotationNames(); Collection<String> metanames = getAvailableMetaNames(); //Code from btInitLanguage: addMenu = new MenuBar(); //addMenu.setDescription(INFO_INIT_LANG); addMenu.setAutoOpen(false); final MenuBar.MenuItem add = addMenu.addItem(ADD_LING_PARAM, null); for (final String annoname : annonames) { add.addItem(annoname, new MenuBar.Command() { @Override public void menuSelected(MenuBar.MenuItem selectedItem) { if (!vnodes.isEmpty()) { EdgeBox eb = new EdgeBox(sq); languagenodes.addComponent(eb); eboxes.add(eb); } VerticalNode vn = new VerticalNode(annoname, sq); languagenodes.addComponent(vn); vnodes.add(vn); addMenu.setAutoOpen(false); } }); } language.removeComponent(btInitLanguage); language.addComponent(addMenu); //Code from btInitSpan: addMenuSpan = new MenuBar(); //addMenuSpan.setDescription(INFO_INIT_SPAN); addMenuSpan.setAutoOpen(false); final MenuBar.MenuItem addSpan = addMenuSpan.addItem(ADD_SPAN_PARAM, null); for (final String annoname : annonames) { addSpan.addItem(annoname, new MenuBar.Command() { @Override public void menuSelected(MenuBar.MenuItem selectedItem) { sq.removeSpanBox(); sq.addSpanBox(annoname); addMenuSpan.setAutoOpen(false); } }); } spanMenu = addSpan; span.removeComponent(btInitSpan); span.addComponent(addMenuSpan); //Code from btInitMeta: addMenuMeta = new MenuBar(); //addMenuMeta.setDescription(INFO_INIT_META); addMenuMeta.setAutoOpen(false); final MenuBar.MenuItem addMeta = addMenuMeta.addItem(ADD_META_PARAM, null); int i = 0; for (final String annoname : metanames) { addMeta.addItem(annoname, new MenuBar.Command() { @Override public void menuSelected(MenuBar.MenuItem selectedItem) { MetaBox mb = new MetaBox(annoname, sq); meta.addComponent(mb); mboxes.add(mb); addMenuMeta.setAutoOpen(false); //addMeta.removeChild(selectedItem); selectedItem.setVisible(false); } }); } meta.removeComponent(btInitMeta); meta.addComponent(addMenuMeta); } private String getAQLFragment(SearchBox sb) { String result = ""; String value = null; try { value = sb.getValue(); } catch (java.lang.NullPointerException ex) { value = null; } String level=sb.getAttribute(); if (value == null){ result = level; } if (value != null){ if (sb.isRegEx() && !sb.isNegativeSearch()) { result = level+"=/"+value.replace("/", "\\x2F") +"/"; } if (sb.isRegEx() && sb.isNegativeSearch()) { result = level+"!=/"+value.replace("/", "\\x2F") +"/"; } if (!sb.isRegEx() && sb.isNegativeSearch()) { result = level+"!=\""+value.replace("\"", "\\x22") +"\""; } if (!sb.isRegEx() && !sb.isNegativeSearch()) { result = level+"=\""+value.replace("\"", "\\x22") +"\""; } } return result; } private String getMetaQueryFragment(MetaBox mb) { Collection<String> values = mb.getValues(); if(!values.isEmpty()) { StringBuilder result = new StringBuilder("\n& meta::"+mb.getMetaDatum()+" = "); if(values.size()==1) { result.append("\""+values.iterator().next().replace("\"", "\\x22")+"\""); } else { Iterator<String> itValues = values.iterator(); result.append("/(" + escapeRegexCharacters(itValues.next())+")"); while(itValues.hasNext()) { result.append("|("+escapeRegexCharacters(itValues.next())+")"); } result.append("/"); } return result.toString(); } return ""; } public String escapeRegexCharacters(String tok) { if(tok==null){return "";} if(tok.equals("")){return "";} String result=tok; for (int i = 0; i<REGEX_CHARACTERS.length; i++) { result = result.replace(REGEX_CHARACTERS[i], "\\"+REGEX_CHARACTERS[i]); } return result.replace("/", "\\x2F"); } public String unescape(String s) { //first unescape slashes and quotes: s = unescapeSlQ(s); //unescape regex characters: int i=1; while(i<s.length()) { char c0 = s.charAt(i-1); char c1 = s.charAt(i); for(int j=0; j<REGEX_CHARACTERS.length; j++) { if( (c1==REGEX_CHARACTERS[j].charAt(0)) & (c0=='\\') ) { s = s.substring(0, i-1) + s.substring(i); break; } if(j==REGEX_CHARACTERS.length-1) { i++; } } } return s; } public String unescapeSlQ(String s) { return s.replace("\\x2F", "/").replace("\\x22", "\""); } private String getAQLQuery() { int count = 1; StringBuilder ql = new StringBuilder(); StringBuilder edgeQuery = new StringBuilder(); StringBuilder sentenceQuery = new StringBuilder(); Collection<Integer> sentenceVars = new ArrayList<>(); Iterator<EdgeBox> itEboxes = eboxes.iterator(); for (VerticalNode v : vnodes) { Collection<SearchBox> sboxes = v.getSearchBoxes(); for (SearchBox s : sboxes) { ql.append(" & " + getAQLFragment(s)); } if (sboxes.isEmpty()) { //not sure we want to do it this way: ql.append("\n& /.*/"); } sentenceVars.add(Integer.valueOf(count)); for(int i=1; i < sboxes.size(); i++) { String addQuery = "\n& #" + count +"_=_"+ "#" + ++count; edgeQuery.append(addQuery); } count++; String edgeQueryAdds = (itEboxes.hasNext()) ? "\n& #"+(count-1)+" " +itEboxes.next().getValue()+" #"+count : ""; edgeQuery.append(edgeQueryAdds); } String addQuery = ""; try { SpanBox spb = (SpanBox) span.getComponent(1); if ((!spb.isRegEx())&&(!spb.getValue().isEmpty())) { addQuery = "\n& "+ spb.getAttribute() + " = \"" + spb.getValue() + "\""; } if (spb.isRegEx()) { addQuery = "\n& "+ spb.getAttribute() + " = /" + spb.getValue().replace("/", "\\x2F") + "/"; } if (spb.getValue().isEmpty()){ addQuery = "\n&" + spb.getAttribute(); } ql.append(addQuery); for(Integer i : sentenceVars) { sentenceQuery.append("\n& #").append(count).append("_i_#").append(i.toString()); } } catch (Exception ex){ ex = null; } StringBuilder metaQuery = new StringBuilder(); Iterator<MetaBox> itMetaBoxes = mboxes.iterator(); while(itMetaBoxes.hasNext()) { metaQuery.append(getMetaQueryFragment(itMetaBoxes.next())); } String fullQuery = (ql.toString() + edgeQuery.toString()+sentenceQuery.toString()+metaQuery.toString()); if (fullQuery.length() < 3) {return "";} fullQuery = fullQuery.substring(3);//deletes leading " & " this.query = fullQuery; return fullQuery; } public void updateQuery() { try{ cp.setQuery(new Query(getAQLQuery(), cp.getState().getSelectedCorpora().getValue())); } catch (java.lang.NullPointerException ex) { Notification.show(INCOMPLETE_QUERY_WARNING); } } @Override public void buttonClick(Button.ClickEvent event) { if (cp.getState().getSelectedCorpora().getValue().isEmpty()) { Notification.show(NO_CORPORA_WARNING); } else { if (event.getButton() == btGo) { updateQuery(); } if (event.getButton() == btClear) { clear(); updateQuery(); launch(cp); } if (event.getComponent() == btInverse) { try { loadQuery(); } catch(EmptyReferenceException e) { log.error(null, e); } catch (EqualityConstraintException e) { log.error(null, e); } catch (InvalidCharacterSequenceException e) { log.error(null, e); } catch (MultipleAssignmentException e) { log.error(null, e); } catch (UnknownLevelException e) { log.error(null, e); } } if (event.getComponent() == btInitMeta || event.getComponent() == btInitSpan || event.getComponent() == btInitLanguage){ initialize(); } } } public void removeVerticalNode(VerticalNode v) { Iterator<VerticalNode> itVnodes = vnodes.iterator(); Iterator<EdgeBox> itEboxes = eboxes.iterator(); VerticalNode vn = itVnodes.next(); EdgeBox eb=null; while(!vn.equals(v)) { vn = itVnodes.next(); eb = itEboxes.next(); } if((eb==null) & (itEboxes.hasNext())) { eb = itEboxes.next(); } vnodes.remove(v); if(eb!=null) { eboxes.remove(eb); languagenodes.removeComponent(eb); } languagenodes.removeComponent(v); } public void addSpanBox(String level) { spbox = new SpanBox(level, this); span.addComponent(spbox); span.setComponentAlignment(spbox, Alignment.MIDDLE_LEFT); spanMenu.setText(CHANGE_SPAN_PARAM); } public void addSpanBox(SpanBox spb) { if(spbox==null) { spanMenu.setText(CHANGE_SPAN_PARAM); } else { span.removeComponent(spbox); } spbox = spb; span.addComponent(spbox); span.setComponentAlignment(spbox, Alignment.MIDDLE_LEFT); } public void removeSpanBox() { if(spbox!=null) { span.removeComponent(spbox); spbox=null; spanMenu.setText(ADD_SPAN_PARAM); } } public void removeSpanBox(SpanBox spb) { if(spb.equals(spbox)) { removeSpanBox(); } } public void removeMetaBox(MetaBox v) { meta.removeComponent(v); mboxes.remove(v); List<MenuBar.MenuItem> items = addMenuMeta.getItems().get(0).getChildren(); boolean found = false; String metalevel = v.getMetaDatum(); for(int i=0; (i<items.size())&!found; i++) { MenuBar.MenuItem itm = items.get(i); if(itm.getText().equals(metalevel)) { itm.setVisible(true); found = true; } } } public Collection<String> getAnnotationValues(String level) { Collection<String> values = new TreeSet<>(); for(String s : getAvailableAnnotationLevels(level)) { values.add(s); } return values; } public Set<String> getAvailableAnnotationNames() { Set<String> result = new TreeSet<>(); WebResource service = Helper.getAnnisWebResource(); // get current corpus selection Set<String> corpusSelection = cp.getState().getSelectedCorpora().getValue(); if (service != null) { try { List<AnnisAttribute> atts = new LinkedList<>(); for(String corpus : corpusSelection) { atts.addAll(service.path("query").path("corpora").path(corpus).path("annotations") .queryParam("fetchvalues", "false") .queryParam("onlymostfrequentvalues", "false") .get(new GenericType<List<AnnisAttribute>>() {}) ); } for (AnnisAttribute a : atts) { if (a.getType() == AnnisAttribute.Type.node) { result.add(killNamespace(a.getName())); } } } catch (ClientHandlerException ex) { log.error(null, ex); } catch (UniformInterfaceException ex) { log.error(null, ex); } } result.add("tok"); return result; } public Collection<String> getAvailableAnnotationLevels(String meta) { Collection<String> result = new TreeSet<>(); WebResource service = Helper.getAnnisWebResource(); // get current corpus selection Set<String> corpusSelection = cp.getState().getSelectedCorpora().getValue(); if (service != null) { try { List<AnnisAttribute> atts = new LinkedList<>(); for(String corpus : corpusSelection) { atts.addAll(service.path("query").path("corpora").path(corpus) .path("annotations") .queryParam("fetchvalues", "true") .queryParam("onlymostfrequentvalues", "false") .get(new GenericType<List<AnnisAttribute>>() {}) ); } for (AnnisAttribute a : atts) { if (a.getType() == AnnisAttribute.Type.node) { String aa = killNamespace(a.getName()); if (aa.equals(meta)) { result.addAll(a.getValueSet()); } } } } catch (ClientHandlerException ex) { log.error(null, ex); } catch (UniformInterfaceException ex) { log.error(null, ex); } } return result; } public String killNamespace(String qName) { String[] splitted = qName.split(":", 2); if (splitted.length > 1){ return splitted[1]; } else{ return qName; } } public Set<String> getAvailableMetaNames() { Set<String> result = new TreeSet<>(); WebResource service = Helper.getAnnisWebResource(); // get current corpus selection Set<String> corpusSelection = cp.getState().getSelectedCorpora().getValue(); if (service != null) { try { List<AnnisAttribute> atts = new LinkedList<>(); for(String corpus : corpusSelection) { atts.addAll(service.path("query").path("corpora").path(corpus) .path("annotations") .get(new GenericType<List<AnnisAttribute>>() {}) ); } for (AnnisAttribute a : atts) { if (a.getType() == AnnisAttribute.Type.meta) { String aa = killNamespace(a.getName()); result.add(aa); } } } catch (ClientHandlerException ex) { log.error(null, ex); } catch (UniformInterfaceException ex) { log.error(null, ex); } } return result; } public Set<String> getAvailableMetaLevels(String meta) { Set<String> result = new TreeSet<>(); WebResource service = Helper.getAnnisWebResource(); // get current corpus selection Set<String> corpusSelection = cp.getState().getSelectedCorpora().getValue(); if (service != null) { try { List<AnnisAttribute> atts = new LinkedList<>(); for(String corpus : corpusSelection) { atts.addAll(service.path("query").path("corpora").path(corpus) .path("annotations") .queryParam("fetchvalues", "true") .queryParam("onlymostfrequentvalues", "false") .get(new GenericType<List<AnnisAttribute>>() {}) ); } for (AnnisAttribute a : atts) { if (a.getType() == AnnisAttribute.Type.meta) { String aa = killNamespace(a.getName()); if (aa.equals(meta)) { result.addAll(a.getValueSet()); } } } } catch (ClientHandlerException ex) { log.error(null, ex); } catch (UniformInterfaceException ex) { log.error(null, ex); } } return result; } public String getFilterMechanism() { return filtering.getItemCaption(filtering.getValue()); } private void clear() //check whether it is necessary to do this in a method { language.removeAllComponents(); span.removeAllComponents(); meta.removeAllComponents(); toolbar.removeAllComponents(); mainLayout.removeComponent(language); mainLayout.removeComponent(span); mainLayout.removeComponent(meta); mainLayout.removeComponent(toolbar); vnodes.clear(); eboxes.clear(); mboxes.clear(); } private Collection<String> splitMultipleValueExpression(String expression) /* * only for complex regex expressions */ { ArrayList<String> values = new ArrayList<>(); String s = expression; while(s.length()>0) { if(s.charAt(0)=='|') {s = s.substring(1);} if(s.charAt(0)!='(') { values = new ArrayList<>(); values.add(expression); return values; } else { int pc = 1; int i = 1; while(pc!=0) { char c = s.charAt(i); if(c==')') {pc--;} else if(c=='(') {pc++;} i++; } values.add(unescapeSlQ(s.substring(1, i-1))); //in respect to removal of parentheses s = s.substring(i); } } return values; } public ReducingStringComparator getRSC() { return rsc; } public void loadQuery() throws UnknownLevelException, EqualityConstraintException, MultipleAssignmentException, InvalidCharacterSequenceException, EmptyReferenceException /* * this method is called by btInverse * When the query has changed in the * textfield, the query represented by * the query builder is not equal to * the one delivered by the text field */ { /*get clean query from control panel text field*/ String tq = cp.getState().getAql().getValue().replace("\n", " ").replace("\r", ""); //TODO VALIDATE QUERY: (NOT SUFFICIENT YET) boolean valid = (!tq.equals("")); if(!(query.equals(tq)) & valid) { //PROBLEM: LINE BREAKS (simple without anything else) try { //the dot marks the end of the string - it is not read, it's just a place holder tq += "."; HashMap<Integer, Constraint> constraints = new HashMap<>(); ArrayList<Relation> pRelations = new ArrayList<>(); ArrayList<Relation> eRelations = new ArrayList<>(); Relation inclusion=null; Constraint conInclusion=null; ArrayList<Constraint> metaConstraints = new ArrayList<>(); //parse typed-in Query //Step 1: get indices of tq-chars, where constraints are separated (&) String tempCon=""; int count = 1; int maxId = 0; boolean inclusionCheck=false; for(int i=0; i<tq.length(); i++) { //improve this Algorithm (compare to constraint) char c = tq.charAt(i); if((c!='&') & (i!=tq.length()-1)) { if(!((tempCon.length()==0) & (c==' '))) //avoids, that a constraint starts with a space { tempCon += c; } } else { while(tempCon.charAt(tempCon.length()-1)==' ') { tempCon = tempCon.substring(0, tempCon.length()-1); } if(tempCon.startsWith("meta::")) { metaConstraints.add(new Constraint(tempCon)); } else if(tempCon.startsWith("#")) { Relation r = new Relation(tempCon); if(r.getType()==RelationType.EQUALITY) { eRelations.add(r); } else if(r.getType()==RelationType.PRECEDENCE) { pRelations.add(r); } else if((r.getType()==RelationType.INCLUSION)&(!inclusionCheck)) { inclusion = r; if(constraints.containsKey(r.getFirst())) { conInclusion = constraints.get(r.getFirst()); constraints.remove(r.getFirst()); } inclusionCheck=true; } int newMax = (r.getFirst()>r.getSecond()) ? r.getFirst() : r.getSecond(); maxId = (maxId<newMax) ? newMax : maxId; } else { constraints.put(count++, new Constraint(tempCon)); } tempCon = ""; } } /*CHECK FOR EMPTY REFERENCE*/ /*IDEA: If the highest element-id is not empty, all lower ids can't be empty*/ /*one additional increment of count has to be taken into account*/ /*empty means, the element the id is refering to does not exist*/ if(maxId>=count) { throw new EmptyReferenceException(Integer.toString(maxId)); } /*CHECK FOR INVALID OR REDUNDANT MUTLIPLE VALUE ASSIGNMENT*/ for(Relation rel : eRelations) { Constraint con1 = constraints.get(rel.getFirst()); Constraint con2 = constraints.get(rel.getSecond()); if(con1.getLevel().equals(con2.getLevel())) { throw new MultipleAssignmentException(con1.toString()+" <-> "+con2.toString()); } } //create Vertical Nodes HashMap<Integer, VerticalNode> indexedVnodes = new HashMap<>(); VerticalNode vn=null; Collection<String> annonames = getAvailableAnnotationNames(); for(int i : constraints.keySet()) { Constraint con = constraints.get(i); if(!annonames.contains(con.getLevel())) { throw new UnknownLevelException(con.getLevel()); //is that a good idea? YES } if(!indexedVnodes.containsKey(i)) { vn = new VerticalNode(con.getLevel(), con.getValue(), this, con.isRegEx(), con.isNegative()); if(con.isRegEx()) { SearchBox sb = vn.getSearchBoxes().iterator().next(); /*CHECK FIRST IF WE REALLY HAVE A MULTIPLE VALUE EXPRESSION*/ Collection<String> mvalue = splitMultipleValueExpression(con.getValue()); if(mvalue.size()==1) { sb.setValue(mvalue.iterator().next()); } else { sb.setValue(mvalue); } } indexedVnodes.put(i, vn); } for(Relation rel : eRelations) { if(rel.contains(i)) { int b = rel.whosMyFriend(i); if(!indexedVnodes.containsKey(b)) { indexedVnodes.put(b, null); Constraint bcon = constraints.get(b); SearchBox sb = new SearchBox(bcon.getLevel(), this, vn, bcon.isRegEx(), bcon.isNegative()); Collection<String> values = splitMultipleValueExpression(bcon.getValue()); if(values.size()>1) { sb.setValue(values); } else { sb.setValue(bcon.getValue()); } vn.addSearchBox(sb); } } } } //clean query builder surface for(VerticalNode v : vnodes) { languagenodes.removeComponent(v); } vnodes.clear(); for(EdgeBox eb : eboxes) { languagenodes.removeComponent(eb); } eboxes.clear(); for(MetaBox mb : mboxes) { meta.removeComponent(mb); } mboxes.clear(); //remove SpanBox removeSpanBox(); VerticalNode first = null; int smP = (!pRelations.isEmpty()) ? pRelations.iterator().next().getFirst() : 0; int smE = (!eRelations.isEmpty()) ? eRelations.iterator().next().getFirst() : 0; if((smP+smE)==0) { if(!indexedVnodes.isEmpty()) { first = indexedVnodes.values().iterator().next(); } } else if((smP!=0) & (smE!=0)) { first = indexedVnodes.get(Math.min(smE, smP)); } else { //one value is zero first = indexedVnodes.get(smE+smP); } if(first!=null) { vnodes.add(first); languagenodes.addComponent(first); for(Relation rel : pRelations) { EdgeBox eb = new EdgeBox(this); eb.setValue(rel.getOperator()); eboxes.add(eb); VerticalNode v = indexedVnodes.get(rel.getSecond()); vnodes.add(v); languagenodes.addComponent(eb); languagenodes.addComponent(v); } } //build SpanBox if(inclusion!=null) { addSpanBox(new SpanBox(conInclusion.getLevel(), this, conInclusion.isRegEx())); spbox.setValue(conInclusion.getValue()); } //build MetaBoxes for(Constraint mc : metaConstraints) { if(mc.isRegEx()) { Collection<String> values = splitMultipleValueExpression(unescape(unescapeSlQ(mc.getValue()))); MetaBox mb = new MetaBox(mc.getLevel(), this); mb.setValue(values); mboxes.add(mb); meta.addComponent(mb); } else { MetaBox mb = new MetaBox(mc.getLevel(), this); //for a particular reason (unknown) setValue() with a String parameter //is not accepted by OptionGroup Collection<String> values = new TreeSet<>(); values.add(unescapeSlQ(mc.getValue())); mb.setValue(values); mboxes.add(mb); meta.addComponent(mb); } } query = tq.substring(0, tq.length()-1); } catch(EmptyReferenceException e) { Notification.show(e.getMessage()); } catch (EqualityConstraintException e) { Notification.show(e.getMessage()); } catch (InvalidCharacterSequenceException e) { Notification.show(e.getMessage()); } catch (MultipleAssignmentException e) { Notification.show(e.getMessage()); } catch (UnknownLevelException e) { Notification.show(e.getMessage()); } } } private class Constraint { private String level; private String value; private boolean regEx; private boolean negative; public Constraint(String s) throws InvalidCharacterSequenceException { int e=0; if(s.contains("=")) { while(s.charAt(e)!='=') { e++; } String l; if(s.charAt(e-1)=='!') { l = s.substring(0, e-1).replace(" ", ""); negative = true; } else { l = s.substring(0, e).replace(" ", "").replace("meta::", ""); negative = false; } String v = s.substring(e+1); while(v.startsWith(" ")) { v = v.substring(1); } if(v.startsWith("\"")) { regEx = false; } else { regEx = true; } //remove " or / : v = v.substring(1, v.length()-1); level = l; value = v; } else if( ((s.charAt(0)=='\"') && (s.charAt(s.length()-1)=='\"')) || ((s.charAt(0)=='/') && (s.charAt(s.length()-1)=='/'))) { level = "tok"; value = s.substring(1, s.length()-1); } else if((s.contains("\""))||(s.contains("/"))) { throw new InvalidCharacterSequenceException(s); } else { level = s; value = ""; } } public String getLevel() { return level; } public String getValue() { return value; } public boolean isRegEx() { return regEx; } public boolean isNegative() { return negative; } @Override public String toString() { String op = (negative) ? "!=" : "="; String val = (regEx) ? "/"+value+"/" : "\""+value+"\""; return level+op+val; } } private class Relation /* * Problems: * if an operator is used, which is not in the EdgeBoxe's list * the programm will crash. Right now. I'm gonna fix this. * */ { //operands without '#' private int o1, o2; private String operator; private RelationType type; public Relation(String in) throws EqualityConstraintException { StringBuilder op = new StringBuilder(); StringBuilder o1str = new StringBuilder(); StringBuilder o2str = new StringBuilder(); int i=1; char c = in.charAt(1); in = in.replace(" ", ""); while((c!='.')&(c!='>')&(c!='_')&(c!='#')&(c!='-')&(c!='$')&(c!='=')) { o1str.append(c); i++; c = in.charAt(i); } while(c!='#') { op.append(c); i++; c = in.charAt(i); } i++; while((i<in.length())) { c = in.charAt(i); o2str.append(c); i++; } operator = op.toString(); o1 = Integer.parseInt(o1str.toString()); o2 = Integer.parseInt(o2str.toString()); if(operator.startsWith(".")) { type = RelationType.PRECEDENCE; } else if((operator.equals("=")) || (operator.equals("_=_"))) { type = RelationType.EQUALITY; if(o1>o2) { int tmp = o1; o1 = o2; o2 = tmp; } else if(o1==o2) { throw new EqualityConstraintException(in); } } else if(operator.equals("_i_")) { type = RelationType.INCLUSION; } } public RelationType getType() { return type; } public int getFirst() { return o1; } public int getSecond() { return o2; } public String getOperator() { return operator; } public boolean contains(int a) { return ((o1==a)|(o2==a)); } public int whosMyFriend(int a) { if(a==o1) { return o2; } if(a==o2) { return o1; } return 0; } } private enum RelationType { PRECEDENCE, DOMINANCE, INCLUSION, EQUALITY } private abstract class LoadQueryException extends Exception { protected String ERROR_MESSAGE; protected String critical; public LoadQueryException(String s) { critical = s; } @Override public String getMessage() { return ERROR_MESSAGE+critical; } } private class UnknownLevelException extends LoadQueryException { public UnknownLevelException(String s) { super(s); ERROR_MESSAGE = "Unknown annotation level: "; } } private class MultipleAssignmentException extends LoadQueryException { public MultipleAssignmentException(String s) { super(s); ERROR_MESSAGE = "Invalid or redundant assignment of multiple values:\n\n"; } } private class EqualityConstraintException extends LoadQueryException { public EqualityConstraintException(String s) { super(s); ERROR_MESSAGE = "Invalid use of equality operator: "; } } private class InvalidCharacterSequenceException extends LoadQueryException { public InvalidCharacterSequenceException(String s) { super(s); ERROR_MESSAGE="Invalid character sequence: \n\n"; } } private class EmptyReferenceException extends LoadQueryException { public EmptyReferenceException(String s) { super(s); ERROR_MESSAGE = "Element not found. Empty reference: #"; } } }