/** * Copyright 2011-2012 Akiban Technologies, Inc. * * 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 com.persistit; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; /** * Select Volumes, Trees or Keys given a pattern string. The CLI utilities use * this to select Volumes and/or Trees. Syntax: * * <pre> * <i>volpattern</i>[:<i>treepattern</i>[<i>keyfilter</i>],... * </pre> * * where <i>volpattern</i> and <i>treepattern</i> are pattern strings that use * "*" and "?" as multi-character and single-character wild-cards. * (Alternatively, if the <code>regex</code> flag is set, these are true regular * expressions.) Example: * * <code><pre> * v1:*index*{"a"-"f"},*data/* * </pre></code> * * selects all trees in volume named "v1" having names containing the substring * "index", and all tress in all values having names that end with "data". For * trees selected in volume v1, there is a keyfilter that specifies keys * starting with letters 'a' through 'f'. * <p /> * The {@link #parseSelector(String, boolean, char)} method takes a quote * character, normally '\\', that may be used to quote the meta characters in * patterns, commas and colons. * * @author peter */ public class TreeSelector { private final static char WILD_MULTI = '*'; private final static char WILD_ONE = '?'; private final static char COMMA = ','; private final static char COLON = ':'; private final static char LBRACE = '{'; private final static char RBRACE = '}'; private final static char NUL = (char) 0; private final static String CAN_BE_QUOTED = "*?,"; private final static String REGEX_QUOTE = "^$*+?()[]."; /** * Constraints on volume name, tree name and/or key. */ private static class Selector { Pattern _vpattern; Pattern _tpattern; KeyFilter _keyFilter; private boolean isNull() { return _vpattern == null && _tpattern == null && _keyFilter == null; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(_vpattern); if (_tpattern != null) { sb.append(COLON); sb.append(_tpattern); if (_keyFilter != null) { sb.append(_keyFilter); } } return sb.toString(); } } private static enum State { V, T, K, C } /** * Create a <code>TreeSelector</code> based on the supplied parameters. * * @param spec * The specification string * @param regex * <code>true</code> if the specification string is a Regex * expression, <code>false</code> if it simply uses '*' and '?' * as wildcards * @param quote * meta-character to quote the next character, typically '\' * @return the <code>TreeSelector</code> */ public static TreeSelector parseSelector(final String spec, final boolean regex, final char quote) { final TreeSelector treeSelector = new TreeSelector(); if ("*".equals(spec)) { return treeSelector; } Selector s = new Selector(); State state = State.V; final StringBuilder sb = new StringBuilder(); boolean quoted = false; final int size = spec.length() + 1; for (int index = 0; index < size; index++) { final char c = index + 1 < size ? spec.charAt(index) : (char) 0; if (quoted) { sb.append(c); quoted = false; } else if (c == quote && index < size - 2 && CAN_BE_QUOTED.indexOf(spec.charAt(index + 1)) > 0) { quoted = true; } else { switch (state) { case V: if (c == NUL && sb.length() == 0) { break; } if (c == WILD_MULTI && !regex) { sb.append(".*"); } else if (c == WILD_ONE && !regex) { sb.append("."); } else if (!regex && REGEX_QUOTE.indexOf(c) != -1) { sb.append('\\'); sb.append(c); } else if (c == COLON || c == COMMA || c == NUL) { s._vpattern = Pattern.compile(sb.toString()); sb.setLength(0); if (c == COLON) { state = State.T; } else { treeSelector._terms.add(s); s = new Selector(); state = State.V; } } else { sb.append(c); } break; case T: if (c == WILD_MULTI && !regex) { sb.append(".*"); } else if (c == WILD_ONE && !regex) { sb.append("."); } else if (!regex && REGEX_QUOTE.indexOf(c) != -1) { sb.append('\\'); sb.append(c); } else if (c == LBRACE || c == COMMA || c == NUL) { s._tpattern = Pattern.compile(sb.toString()); sb.setLength(0); if (c == LBRACE) { state = State.K; sb.append(c); } else { treeSelector._terms.add(s); s = new Selector(); state = State.V; } } else { sb.append(c); } break; case K: sb.append(c); if (c == RBRACE || c == NUL) { s._keyFilter = new KeyParser(sb.toString()).parseKeyFilter(); treeSelector._terms.add(s); s = new Selector(); sb.setLength(0); state = State.C; } break; case C: if (c == COMMA || c == NUL) { state = State.V; } else { throw new IllegalArgumentException("at index=" + index); } } } } return treeSelector; } private final List<Selector> _terms = new ArrayList<Selector>(); public boolean isSelectAll() { return _terms.isEmpty(); } public int size() { return _terms.size(); } public boolean isSelected(final Volume volume) { return isVolumeNameSelected(volume.getName()); } public boolean isSelected(final Tree tree) { return isTreeNameSelected(tree.getVolume().getName(), tree.getName()); } public boolean isVolumeNameSelected(final String volumeName) { if (_terms.isEmpty()) { return true; } for (final Selector selector : _terms) { if (selector._vpattern == null || selector._vpattern.matcher(volumeName).matches()) { return true; } } return false; } public boolean isVolumeOnlySelection(final String volumeName) { if (_terms.isEmpty()) { return true; } for (final Selector selector : _terms) { if ((selector._vpattern == null || selector._vpattern.matcher(volumeName).matches()) && selector._tpattern == null && selector._keyFilter == null) { return true; } } return false; } public boolean isTreeNameSelected(final String volumeName, final String treeName) { if (_terms.isEmpty()) { return true; } for (final Selector selector : _terms) { if ((selector._vpattern == null || selector._vpattern.matcher(volumeName).matches()) && (selector._tpattern == null || selector._tpattern.matcher(treeName).matches())) { return true; } } return false; } public KeyFilter keyFilter(final String volumeName, final String treeName) { KeyFilter kf = null; for (final Selector selector : _terms) { if ((selector._vpattern == null || selector._vpattern.matcher(volumeName).matches()) && (selector._tpattern == null || selector._tpattern.matcher(treeName).matches())) { if (kf == null) { kf = selector._keyFilter; if (kf == null) { kf = new KeyFilter(); } } else { throw new IllegalStateException("Non-unique KeyFilters for tree " + volumeName + "/" + treeName); } } } return kf; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); for (final Selector ts : _terms) { if (sb.length() > 0) { sb.append(COMMA); } sb.append(ts.toString()); } return sb.toString(); } }