/* * Copyright 2009-2011 Collaborative Research Centre SFB 632 * * 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.service.objects; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.common.escape.Escaper; import com.google.common.escape.Escapers; import com.google.common.net.PercentEscaper; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.xml.bind.annotation.XmlAccessOrder; import javax.xml.bind.annotation.XmlAccessorOrder; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a single match of an AQL query. * * @author Thomas Krause <krauseto@hu-berlin.de> */ @XmlRootElement @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL) public class Match implements Serializable { private final static Logger log = LoggerFactory.getLogger(Match.class); private final static Splitter matchSplitter = Splitter.on(" ").trimResults().omitEmptyStrings(); private final static Splitter annoIDSplitter = Splitter.on("::").trimResults().limit(3); private static final Escaper spaceEscaper = Escapers.builder() .addEscape(' ', "%20") .addEscape('%', "%25") // also encode the percent itself .build(); private List<URI> saltIDs; private List<String> annos; public Match() { saltIDs = new ArrayList<>(); annos = new ArrayList<>(); } public Match(Collection<URI> originalIDs) { saltIDs = new ArrayList<>(originalIDs); annos = new ArrayList<>(saltIDs.size()); for (URI saltID : saltIDs) { annos.add(""); } } public Match(Collection<URI> originalIDs, Collection<String> originalAnnos) { saltIDs = new ArrayList<>(originalIDs); annos = new ArrayList<>(originalAnnos); } public void addSaltId(URI id) { addSaltId(id, null); } public void addSaltId(URI id, String anno) { if(id != null) { saltIDs.add(id); if(anno == null) { annos.add(""); } else { annos.add(anno); } } } /** * Get Salt IDs of the nodes that are part of the match. * @return */ @XmlElement(name="id") public List<URI> getSaltIDs() { return saltIDs; } /** * @see #getSaltIDs() * @param saltIDs */ public void setSaltIDs(List<URI> saltIDs) { this.saltIDs = saltIDs; } /** * Get the fully qualified annotation matched annotation names. * This list must be the same size as {@link #getSaltIDs() }. * If no annotation is matched, the list contains an entry with an empty string. * @return */ @XmlElement(name="anno") public List<String> getAnnos() { if(annos == null || annos.size() != saltIDs.size()) { createEmptyAnnoList(); } return annos; } public void setAnnos(List<String> annos) { this.annos = annos; } private void createEmptyAnnoList() { if(saltIDs != null) { annos = new ArrayList<>(saltIDs.size()); for (URI saltID : saltIDs) { annos.add(""); } } } public static Match parseFromString(String raw) { return parseFromString(raw, ' '); } public static Match parseFromString(String raw, char separator) { Match match = new Match(); Splitter splitter = matchSplitter; if(separator != ' ') { splitter = Splitter.on(separator).trimResults().omitEmptyStrings(); } for (String singleMatch : splitter.split(raw)) { URI uri; // undo any escaping singleMatch = singleMatch.replace("%20", " ").replace("%25", "%"); String id = ""; String anno = null; if(singleMatch.startsWith("salt:/")) { id = singleMatch; } else { // split into the annotation namespace/name and the salt URI List<String> components = annoIDSplitter.splitToList(singleMatch); int componentsSize = components.size(); Preconditions.checkArgument(componentsSize == 3 || componentsSize == 2, "A match containing " + "annotation information always has to have the form " + "ns::name::salt:/.... or name::salt:/...."); String ns = ""; String name = ""; if(componentsSize == 3) { id = components.get(2); ns = components.get(0); name = components.get(1); } else if(componentsSize == 2) { id = components.get(1); name = components.get(0); } if(ns.isEmpty()) { anno = name; } else { anno = ns + "::" + name; } } try { uri = new java.net.URI(id).normalize(); if (!"salt".equals(uri.getScheme()) || uri.getFragment() == null) { throw new URISyntaxException("not a Salt id", uri.toString()); } // check if the path ends with "/" (which was wrongly used by older ANNIS versions) String path = uri.getPath(); if(path.endsWith("/")) { path = path.substring(0, path.length()-1); uri = new URI(uri.getScheme(), uri.getHost(), path, uri.getFragment()); } } catch (URISyntaxException ex) { log.error("Invalid syntax for ID " + singleMatch, ex); continue; } match.addSaltId(uri, anno); } return match; } /** * Returns a space seperated list of all Salt IDs. * @return */ @Override public String toString() { if(saltIDs != null && annos != null) { Iterator<URI> itID = saltIDs.iterator(); Iterator<String> itAnno = annos.iterator(); LinkedList<String> asString = new LinkedList<>(); while(itID.hasNext() && itAnno.hasNext()) { URI u = itID.next(); String anno = itAnno.next(); if(u != null) { asString.add(singleMatchToString(u, anno)); } } return Joiner.on(" ").join(asString); } return ""; } public static String singleMatchToString(URI uri, String anno) { if(uri != null) { String v = uri.toASCIIString(); if(anno != null && !anno.isEmpty()) { v = spaceEscaper.escape(anno) + "::" + uri; } return v; } return ""; } }