/* * Part of the CCNx Java Library. * * Copyright (C) 2008-2013 Palo Alto Research Center, Inc. * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. You should have received * a copy of the GNU Lesser General Public License along with this library; * if not, write to the Free Software Foundation, Inc., 51 Franklin Street, * Fifth Floor, Boston, MA 02110-1301 USA. */ package org.ccnx.ccn.protocol; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Arrays; import org.ccnx.ccn.impl.encoding.CCNProtocolDTags; import org.ccnx.ccn.impl.encoding.GenericXMLEncodable; import org.ccnx.ccn.impl.encoding.XMLDecoder; import org.ccnx.ccn.impl.encoding.XMLEncodable; import org.ccnx.ccn.impl.encoding.XMLEncoder; import org.ccnx.ccn.impl.support.ByteArrayCompare; import org.ccnx.ccn.io.content.ContentDecodingException; import org.ccnx.ccn.io.content.ContentEncodingException; /** * Exclude filters are used during Interest matching to exclude content. * The filter works on the name component after the last one specified in the Interest. * * Exclude filters contain at least one element. The elements are either a name component, * a bloom filter or the 'any' element. This allows the specification of individual component values to be * excluded, as well as arbitrary ranges of component values and a compact form for long lists of * component values (bloom filters). * * The order of elements within an exclude filter must follow 2 rules: * 1. Within an exclude filter all name component elements must be in ascending order wherever they occur * and there should be no duplicates. * 2. An any element or a bloom filter element must not be followed by an any element or bloom filter. * @see Filler * I.E. Any elements or bloom filters must be separated by at least one name component element. */ public class Exclude extends GenericXMLEncodable implements XMLEncodable, Comparable<Exclude> { public static int OPTIMUM_FILTER_SIZE = 100; /** * Object to contain elements used in an exclude filter. These elements are either * name components, or 'filler' elements - either Any or a BloomFilter. */ public static abstract class Element extends GenericXMLEncodable implements XMLEncodable { } /** * A filler element occurs in a Exclude filter between 2 name components which may be * an implied name component if the filler element is the first or last element in the Exclude filter. * If a filler element is the first element in an Exclude filter there is an implied name component * before it that matches the first possible name component (in name component ordering). Equally * If the last element in an Exclude filter the implied name component after it is the last possible * name component (in name component ordering). */ public static abstract class Filler extends Element { public abstract boolean match(byte [] component); } protected ArrayList<Element> _values = new ArrayList<Element>(); /** * @param values Must be a list of ExcludeElements - Components must be in increasing order * and there must not be more than one BloomFilter in a row. * @throws IllegalArgumentException */ public Exclude(ArrayList<Element> values) { // Make sure the values are valid ExcludeComponent c = null; Element last = null; for (Element ee : values) { if (ee instanceof ExcludeComponent) { ExcludeComponent ec = (ExcludeComponent) ee; // Components must be in increasing order, and no duplicates. if (c != null && ec.compareTo(c) <=0) { //getting this error... adding more debugging information String errorMessage = "out of order or duplicate component element: comparing "+Component.printURI(c.getComponent())+" and "+Component.printURI(ec.getComponent()); throw new InvalidParameterException(errorMessage); } c = ec; } else if (last instanceof Filler) // do not allow 2 fillers in a row throw new InvalidParameterException("bloom filters or anys are not allowed to follow each other"); last = ee; } _values.addAll(values); } /** * Create an Exclude filter that excludes exactly the listed name components. * @param omissions The name components to be excluded. Passing in null or a zero length array * here will result in an IllegalArgumentException exception * @throws IllegalArgumentException */ public Exclude(byte omissions[][]) { if (omissions == null || omissions.length == 0) throw new IllegalArgumentException("No omissions"); Arrays.sort(omissions, new ByteArrayCompare()); for (byte[] omission : omissions) { _values.add(new ExcludeComponent(omission)); } } public Exclude() {} // for use by decoders /** * Create an Exclude filter that excludes all components up to and including the one given, * but none after. * @param component if a null component is passed in then null is returned. */ public static Exclude uptoFactory(byte [] component) { if ( component == null) return null; Exclude ef = new Exclude(); synchronized (ef._values) { ef._values.add(new ExcludeAny()); ef._values.add(new ExcludeComponent(component)); } return ef; } /** * @param omissions List of names to exclude, or null * @return returns null if list is null or empty, or a new Exclude filter that excludes the listed names. * @see Exclude(byte [][]) */ public static Exclude factory(byte omissions [][] ) { if (omissions == null || omissions.length == 0) return null; return new Exclude(omissions); } /** * @param component - A name component * @return true if this component would be excluded by the exclude filter */ public boolean match(byte [] component) { Filler lastFiller = null; synchronized (_values) { for (Element ee : _values) { if (ee instanceof ExcludeComponent) { ExcludeComponent ec = (ExcludeComponent) ee; int res = ec.compareTo(component); if (res == 0) { // we exactly matched a component in the filter return true; } else if (res > 0) { // we reached a component in the filter that is lexicographically after than the one // we're looking for so check if there was a filler between the last component // we saw and this one. return lastFiller != null && lastFiller.match(component); } lastFiller = null; } else { // The element is not a component - so track what filler it was. lastFiller = (Filler) ee; } } } return lastFiller != null && lastFiller.match(component); } /** * Return a new Exclude filter that is a copy of this one with * the supplied omissions added. * @param omissions name components to be excluded. * @return new Exclude filter object or null in case of error */ public void add(byte omissions[][] ) { if (omissions == null || omissions.length == 0) return; Arrays.sort(omissions, new ByteArrayCompare()); /* * i is an outer loop on the omissions list * j is an inner loop on the ExcludeEntries */ int i = 0, j = 0; byte [] omission; Element ee; Filler lastFiller = null; synchronized (_values) { for(;i<omissions.length && j<_values.size();) { omission = omissions[i]; ee = _values.get(j); if (ee instanceof ExcludeComponent) { ExcludeComponent ec = (ExcludeComponent) ee; int res = ec.compareTo(omission); if (res > 0) { // we reached a component in the filter that is lexigraphically after than the one // we're looking for. if (lastFiller != null && lastFiller.match(omission)) { // the filler already matches the component, no need to add it! i++; continue; } // no bloom or the bloom does not match - so add the component explicitly _values.add(j, new ExcludeComponent(omission)); j++; // skip the component we just added if (lastFiller != null) { // there was a non matching bloom, so copy it to ensure same values get excluded // TODO: should this be a clone()? _values.add(j, lastFiller); j++; // skip the bloom we just added } i++; continue; } else if (res == 0) { // we matched a component already in the filter, so no need to add one in, just skip it. i++; continue; } lastFiller = null; } else lastFiller = (Filler) ee; j++; } // if we have values still to add, then add them to the end of the list for(;i<omissions.length;i++) { omission = omissions[i]; _values.add(new ExcludeComponent(omission)); } } } /** * Take an existing Exclude filter and additionally exclude all components up to and including the * component passed in. Useful for updating filters during incremental searches. E.G. for version * number components. * @param component if null then the Exclude filter is left unchanged. */ public void excludeUpto(byte [] component) { if (component == null) return; Filler lastFiller = null; synchronized (_values) { int res = -2; int removes = 0; for (Element ee : _values) { if (ee instanceof ExcludeComponent) { ExcludeComponent ec = (ExcludeComponent) ee; res = ec.compareTo(component); if (res >= 0) break; lastFiller = null; } else { // The element is not a component - so track what filler it was. lastFiller = (Filler) ee; } removes++; } for (int i = 0; i < removes; i++) _values.remove(0); if (res == 0) { // we exactly matched a component already in the filter // prefix it with an Any element, and we're done. _values.add(0, new ExcludeAny()); } else { if (lastFiller == null) { // there was no filler, so prefix the list with an Any and the component, and we're done _values.add(0, new ExcludeAny()); _values.add(1, new ExcludeComponent(component)); return; } if (lastFiller instanceof ExcludeAny) { _values.add(0, new ExcludeAny()); return; } _values.add(0, new ExcludeAny()); _values.add(1, new ExcludeComponent(component)); _values.add(2, lastFiller); } } return; } /** * Check for exclude with no elements * @return true if exclude has no elements */ public boolean empty() { synchronized (_values) { return _values.isEmpty(); } } public void decode(XMLDecoder decoder) throws ContentDecodingException { decoder.readStartElement(getElementLabel()); synchronized (_values) { boolean component; boolean any = false; while ((component = decoder.peekStartElement(CCNProtocolDTags.Component)) || (any = decoder.peekStartElement(CCNProtocolDTags.Any)) || decoder.peekStartElement(CCNProtocolDTags.Bloom)) { @SuppressWarnings("deprecation") Element ee = component?new ExcludeComponent(): any ? new ExcludeAny() : new BloomFilter(); ee.decode(decoder); _values.add(ee); } decoder.readEndElement(); } } public void encode(XMLEncoder encoder) throws ContentEncodingException { if (!validate()) { throw new ContentEncodingException("Cannot encode " + this.getClass().getName() + ": field values missing."); } // if everything is null, output nothing if (empty()) return; encoder.writeStartElement(getElementLabel()); synchronized (_values) { for (Element element : _values) element.encode(encoder); } encoder.writeEndElement(); } @Override public long getElementLabel() { return CCNProtocolDTags.Exclude; } @Override public boolean validate() { // everything can be null return true; } public int compareTo(Exclude o) { int result = 0; if (empty() && !o.empty()) return -1; if (!empty()) { if (o.empty()) return 1; synchronized (_values) { result = _values.size() - o._values.size(); } // TODO: need a better definition of ordering between exclude filters // it's definitely an error to report they are the same just based on length // but first - is this ever used? } return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Exclude other = (Exclude) obj; synchronized (_values) { return _values.equals(other._values); } } public int hashCode() { return _values.hashCode(); } /** * Gets the number of elements in the Exclude filter * @return number of elements */ public int size() { synchronized (_values) { return _values.size(); } } /** * DEBUGGING ONLY -- may need to be removed. */ public Element value(int i) { synchronized(_values) { return _values.get(i); } } public String toString() { StringBuffer sb = new StringBuffer(); boolean first = true; synchronized (_values) { for (Element ee : _values) { if (first) first = false; else sb.append(","); if (ee instanceof ExcludeComponent) { ExcludeComponent ec = (ExcludeComponent) ee; sb.append(Component.printURI(ec.body)); } else { sb.append("B"); } } } return sb.toString(); } }