| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.felix.utils.resource; |
| |
| import org.apache.felix.utils.version.VersionTable; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Version; |
| import org.osgi.resource.Capability; |
| import org.osgi.resource.Requirement; |
| |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.TreeMap; |
| |
| @SuppressWarnings("rawtypes") |
| public class CapabilitySet { |
| |
| private static final Class<?>[] STRING_CLASS = new Class[] {String.class}; |
| |
| private final Map<String, Map<Object, Set<Capability>>> indices; |
| private final Set<Capability> capSet = new HashSet<>(); |
| |
| public CapabilitySet(List<String> indexProps) { |
| indices = new TreeMap<>(); |
| for (int i = 0; (indexProps != null) && (i < indexProps.size()); i++) { |
| indices.put(indexProps.get(i), new HashMap<Object, Set<Capability>>()); |
| } |
| } |
| |
| public void dump() { |
| for (Entry<String, Map<Object, Set<Capability>>> entry : indices.entrySet()) { |
| boolean header1 = false; |
| for (Entry<Object, Set<Capability>> entry2 : entry.getValue().entrySet()) { |
| boolean header2 = false; |
| for (Capability cap : entry2.getValue()) { |
| if (!header1) { |
| System.out.println(entry.getKey() + ":"); |
| header1 = true; |
| } |
| if (!header2) { |
| System.out.println(" " + entry2.getKey()); |
| header2 = true; |
| } |
| System.out.println(" " + cap); |
| } |
| } |
| } |
| } |
| |
| public void addCapability(Capability cap) { |
| capSet.add(cap); |
| |
| // Index capability. |
| for (Entry<String, Map<Object, Set<Capability>>> entry : indices.entrySet()) { |
| Object value = cap.getAttributes().get(entry.getKey()); |
| if (value != null) { |
| if (value.getClass().isArray()) { |
| value = convertArrayToList(value); |
| } |
| |
| Map<Object, Set<Capability>> index = entry.getValue(); |
| |
| if (value instanceof Collection) { |
| Collection c = (Collection) value; |
| for (Object o : c) { |
| indexCapability(index, cap, o); |
| } |
| } else { |
| indexCapability(index, cap, value); |
| } |
| } |
| } |
| } |
| |
| private void indexCapability(Map<Object, Set<Capability>> index, Capability cap, Object capValue) { |
| // TODO: when JDK8, should be: |
| // TODO: index.computeIfAbsent(capValue, k -> new HashSet<>()).add(cap); |
| Set<Capability> set = index.get(capValue); |
| if (set == null) { |
| set = new HashSet<>(); |
| index.put(capValue, set); |
| } |
| set.add(cap); |
| } |
| |
| public void removeCapability(Capability cap) { |
| if (capSet.remove(cap)) { |
| for (Entry<String, Map<Object, Set<Capability>>> entry : indices.entrySet()) { |
| Object value = cap.getAttributes().get(entry.getKey()); |
| if (value != null) { |
| if (value.getClass().isArray()) { |
| value = convertArrayToList(value); |
| } |
| |
| Map<Object, Set<Capability>> index = entry.getValue(); |
| |
| if (value instanceof Collection) { |
| Collection c = (Collection) value; |
| for (Object o : c) { |
| deindexCapability(index, cap, o); |
| } |
| } else { |
| deindexCapability(index, cap, value); |
| } |
| } |
| } |
| } |
| } |
| |
| private void deindexCapability( |
| Map<Object, Set<Capability>> index, Capability cap, Object value) { |
| Set<Capability> caps = index.get(value); |
| if (caps != null) { |
| caps.remove(cap); |
| if (caps.isEmpty()) { |
| index.remove(value); |
| } |
| } |
| } |
| |
| public Set<Capability> match(SimpleFilter sf, boolean obeyMandatory) { |
| Set<Capability> matches = match(capSet, sf); |
| return obeyMandatory |
| ? matchMandatory(matches, sf) |
| : matches; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Set<Capability> match(Set<Capability> caps, SimpleFilter sf) { |
| Set<Capability> matches = new HashSet<>(); |
| |
| if (sf.getOperation() == SimpleFilter.MATCH_ALL) { |
| matches.addAll(caps); |
| } else if (sf.getOperation() == SimpleFilter.AND) { |
| // Evaluate each subfilter against the remaining capabilities. |
| // For AND we calculate the intersection of each subfilter. |
| // We can short-circuit the AND operation if there are no |
| // remaining capabilities. |
| List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue(); |
| for (int i = 0; (caps.size() > 0) && (i < sfs.size()); i++) { |
| matches = match(caps, sfs.get(i)); |
| caps = matches; |
| } |
| } else if (sf.getOperation() == SimpleFilter.OR) { |
| // Evaluate each subfilter against the remaining capabilities. |
| // For OR we calculate the union of each subfilter. |
| List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue(); |
| for (SimpleFilter sf1 : sfs) { |
| matches.addAll(match(caps, sf1)); |
| } |
| } else if (sf.getOperation() == SimpleFilter.NOT) { |
| // Evaluate each subfilter against the remaining capabilities. |
| // For OR we calculate the union of each subfilter. |
| matches.addAll(caps); |
| List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue(); |
| for (SimpleFilter sf1 : sfs) { |
| matches.removeAll(match(caps, sf1)); |
| } |
| } else { |
| Map<Object, Set<Capability>> index = indices.get(sf.getName()); |
| if ((sf.getOperation() == SimpleFilter.EQ) && (index != null)) { |
| Set<Capability> existingCaps = index.get(sf.getValue()); |
| if (existingCaps != null) { |
| matches.addAll(existingCaps); |
| matches.retainAll(caps); |
| } |
| } else { |
| for (Capability cap : caps) { |
| Object lhs = cap.getAttributes().get(sf.getName()); |
| if (lhs != null) { |
| if (compare(lhs, sf.getValue(), sf.getOperation())) { |
| matches.add(cap); |
| } |
| } |
| } |
| } |
| } |
| |
| return matches; |
| } |
| |
| public static boolean matches(Capability capability, Requirement requirement) { |
| return Objects.equals(capability.getNamespace(), requirement.getNamespace()) |
| && matches(capability, RequirementImpl.getFilter(requirement)); |
| } |
| |
| public static boolean matches(Capability cap, SimpleFilter sf) { |
| return matchesInternal(cap, sf) && matchMandatory(cap, sf); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static boolean matchesInternal(Capability cap, SimpleFilter sf) { |
| boolean matched = true; |
| |
| if (sf.getOperation() == SimpleFilter.MATCH_ALL) { |
| matched = true; |
| } else if (sf.getOperation() == SimpleFilter.AND) { |
| // Evaluate each subfilter against the remaining capabilities. |
| // For AND we calculate the intersection of each subfilter. |
| // We can short-circuit the AND operation if there are no |
| // remaining capabilities. |
| List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue(); |
| for (int i = 0; matched && (i < sfs.size()); i++) { |
| matched = matchesInternal(cap, sfs.get(i)); |
| } |
| } else if (sf.getOperation() == SimpleFilter.OR) { |
| // Evaluate each subfilter against the remaining capabilities. |
| // For OR we calculate the union of each subfilter. |
| matched = false; |
| List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue(); |
| for (int i = 0; !matched && (i < sfs.size()); i++) { |
| matched = matchesInternal(cap, sfs.get(i)); |
| } |
| } else if (sf.getOperation() == SimpleFilter.NOT) { |
| // Evaluate each subfilter against the remaining capabilities. |
| // For OR we calculate the union of each subfilter. |
| List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue(); |
| for (SimpleFilter sf1 : sfs) { |
| matched = !(matchesInternal(cap, sf1)); |
| } |
| } else { |
| matched = false; |
| Object lhs = cap.getAttributes().get(sf.getName()); |
| if (lhs != null) { |
| matched = compare(lhs, sf.getValue(), sf.getOperation()); |
| } |
| } |
| |
| return matched; |
| } |
| |
| private static Set<Capability> matchMandatory( |
| Set<Capability> caps, SimpleFilter sf) { |
| for (Iterator<Capability> it = caps.iterator(); it.hasNext();) { |
| Capability cap = it.next(); |
| if (!matchMandatory(cap, sf)) { |
| it.remove(); |
| } |
| } |
| return caps; |
| } |
| |
| private static boolean matchMandatory(Capability cap, SimpleFilter sf) { |
| if (cap instanceof CapabilityImpl) { |
| for (Entry<String, Object> entry : cap.getAttributes().entrySet()) { |
| if (((CapabilityImpl) cap).isAttributeMandatory(entry.getKey()) |
| && !matchMandatoryAttribute(entry.getKey(), sf)) { |
| return false; |
| } |
| } |
| } else { |
| String value = cap.getDirectives().get(Constants.MANDATORY_DIRECTIVE); |
| if (value != null) { |
| List<String> names = ResourceBuilder.parseDelimitedString(value, ","); |
| for (Entry<String, Object> entry : cap.getAttributes().entrySet()) { |
| if (names.contains(entry.getKey()) |
| && !matchMandatoryAttribute(entry.getKey(), sf)) { |
| return false; |
| } |
| } |
| } |
| |
| } |
| return true; |
| } |
| |
| private static boolean matchMandatoryAttribute(String attrName, SimpleFilter sf) { |
| if ((sf.getName() != null) && sf.getName().equals(attrName)) { |
| return true; |
| } else if (sf.getOperation() == SimpleFilter.AND) { |
| List list = (List) sf.getValue(); |
| for (Object aList : list) { |
| SimpleFilter sf2 = (SimpleFilter) aList; |
| if ((sf2.getName() != null) |
| && sf2.getName().equals(attrName)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static boolean compare(Object lhs, Object rhsUnknown, int op) { |
| if (lhs == null) { |
| return false; |
| } |
| |
| // If this is a PRESENT operation, then just return true immediately |
| // since we wouldn't be here if the attribute wasn't present. |
| if (op == SimpleFilter.PRESENT) { |
| return true; |
| } |
| |
| // If the type is comparable, then we can just return the |
| // result immediately. |
| if (lhs instanceof Comparable) { |
| // Spec says SUBSTRING is false for all types other than string. |
| if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String)) { |
| return false; |
| } |
| |
| Object rhs; |
| if (op == SimpleFilter.SUBSTRING) { |
| rhs = rhsUnknown; |
| } else { |
| try { |
| rhs = coerceType(lhs, (String) rhsUnknown); |
| } catch (Exception ex) { |
| return false; |
| } |
| } |
| |
| switch (op) { |
| case SimpleFilter.EQ: |
| try { |
| return ((Comparable) lhs).compareTo(rhs) == 0; |
| } catch (Exception ex) { |
| return false; |
| } |
| case SimpleFilter.GTE: |
| try { |
| return ((Comparable) lhs).compareTo(rhs) >= 0; |
| } catch (Exception ex) { |
| return false; |
| } |
| case SimpleFilter.LTE: |
| try { |
| return ((Comparable) lhs).compareTo(rhs) <= 0; |
| } catch (Exception ex) { |
| return false; |
| } |
| case SimpleFilter.APPROX: |
| return compareApproximate(lhs, rhs); |
| case SimpleFilter.SUBSTRING: |
| return SimpleFilter.compareSubstring((List<String>) rhs, (String) lhs); |
| default: |
| throw new RuntimeException("Unknown comparison operator: " + op); |
| } |
| } |
| |
| // If the LHS is not a comparable or boolean, check if it is an |
| // array. If so, convert it to a list so we can treat it as a |
| // collection. |
| if (lhs.getClass().isArray()) { |
| lhs = convertArrayToList(lhs); |
| } |
| |
| // If LHS is a collection, then call compare() on each element |
| // of the collection until a match is found. |
| if (lhs instanceof Collection) { |
| for (Object o : (Collection) lhs) { |
| if (compare(o, rhsUnknown, op)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // Spec says SUBSTRING is false for all types other than string. |
| if (op == SimpleFilter.SUBSTRING) { |
| return false; |
| } |
| |
| // Since we cannot identify the LHS type, then we can only perform |
| // equality comparison. |
| try { |
| return lhs.equals(coerceType(lhs, (String) rhsUnknown)); |
| } catch (Exception ex) { |
| return false; |
| } |
| } |
| |
| private static boolean compareApproximate(Object lhs, Object rhs) { |
| if (rhs instanceof String) { |
| return removeWhitespace((String) lhs) |
| .equalsIgnoreCase(removeWhitespace((String) rhs)); |
| } else if (rhs instanceof Character) { |
| return Character.toLowerCase((Character) lhs) |
| == Character.toLowerCase((Character) rhs); |
| } |
| return lhs.equals(rhs); |
| } |
| |
| private static String removeWhitespace(String s) { |
| StringBuilder sb = new StringBuilder(s.length()); |
| for (int i = 0; i < s.length(); i++) { |
| if (!Character.isWhitespace(s.charAt(i))) { |
| sb.append(s.charAt(i)); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| private static Object coerceType(Object lhs, String rhsString) throws Exception { |
| // If the LHS expects a string, then we can just return |
| // the RHS since it is a string. |
| if (lhs.getClass() == rhsString.getClass()) { |
| return rhsString; |
| } |
| |
| // Try to convert the RHS type to the LHS type by using |
| // the string constructor of the LHS class, if it has one. |
| Object rhs; |
| try { |
| if (lhs instanceof Version) { |
| rhs = VersionTable.getVersion(rhsString, false); |
| } else |
| // The Character class is a special case, since its constructor |
| // does not take a string, so handle it separately. |
| if (lhs instanceof Character) { |
| rhs = rhsString.charAt(0); |
| } else { |
| // Spec says we should trim number types. |
| if ((lhs instanceof Number) || (lhs instanceof Boolean)) { |
| rhsString = rhsString.trim(); |
| } |
| Constructor ctor = lhs.getClass().getConstructor(STRING_CLASS); |
| ctor.setAccessible(true); |
| rhs = ctor.newInstance(rhsString); |
| } |
| } catch (Exception ex) { |
| throw new Exception( |
| "Could not instantiate class " |
| + lhs.getClass().getName() |
| + " from string constructor with argument '" |
| + rhsString + "' because " + ex |
| ); |
| } |
| |
| return rhs; |
| } |
| |
| /** |
| * This is an ugly utility method to convert an array of primitives |
| * to an array of primitive wrapper objects. This method simplifies |
| * processing LDAP filters since the special case of primitive arrays |
| * can be ignored. |
| * |
| * @param array An array of primitive types. |
| * @return An corresponding array using pritive wrapper objects. |
| */ |
| private static List<Object> convertArrayToList(Object array) { |
| int len = Array.getLength(array); |
| List<Object> list = new ArrayList<>(len); |
| for (int i = 0; i < len; i++) { |
| list.add(Array.get(array, i)); |
| } |
| return list; |
| } |
| } |