http://git-wip-us.apache.org/repos/asf/karaf/blob/1bcdb173/region/src/main/java/org/apache/karaf/region/persist/internal/util/ManifestHeaderProcessor.java ---------------------------------------------------------------------- diff --git a/region/src/main/java/org/apache/karaf/region/persist/internal/util/ManifestHeaderProcessor.java b/region/src/main/java/org/apache/karaf/region/persist/internal/util/ManifestHeaderProcessor.java new file mode 100644 index 0000000..410121c --- /dev/null +++ b/region/src/main/java/org/apache/karaf/region/persist/internal/util/ManifestHeaderProcessor.java @@ -0,0 +1,661 @@ +/* + * 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.karaf.region.persist.internal.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.osgi.framework.Constants; + +public class ManifestHeaderProcessor +{ + public static final String NESTED_FILTER_ATTRIBUTE = "org.apache.aries.application.filter.attribute"; + private static final Pattern FILTER_ATTR = Pattern.compile("(\\(!)?\\((.*?)([<>]?=)(.*?)\\)\\)?"); + private static final String LESS_EQ_OP = "<="; + private static final String GREATER_EQ_OP = ">="; + + /** + * A simple class to associate two types. + * + */ + public static class NameValuePair { + private String name; + private Map<String,String> attributes; + + public NameValuePair(String name, Map<String,String> value) + { + this.name = name; + this.attributes = value; + } + public String getName() + { + return name; + } + public void setName(String name) + { + this.name = name; + } + + public Map<String,String> getAttributes() + { + return attributes; + } + public void setAttributes(Map<String,String> value) + { + this.attributes = value; + } + + @Override + public String toString(){ + return "{"+name.toString()+"::"+attributes.toString()+"}"; + } + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((attributes == null) ? 0 : attributes.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + final NameValuePair other = (NameValuePair) obj; + if (name == null) { + if (other.name != null) return false; + } else if (!name.equals(other.name)) return false; + if (attributes == null) { + if (other.attributes != null) return false; + } else if (!attributes.equals(other.attributes)) return false; + return true; + } + } + + /** + * Intended to provide a standard way to add Name/Value's to + * aggregations of Name/Value's. + * + */ + public static interface NameValueCollection { + /** + * Add this Name & Value to the collection. + * @param n + * @param v + */ + public void addToCollection(String n, Map<String,String> v); + } + + /** + * Map of Name -> Value. + * + */ + public static class NameValueMap extends HashMap<String, Map<String,String>> implements NameValueCollection, Map<String, Map<String,String>>{ + private static final long serialVersionUID = -6446338858542599141L; + + public void addToCollection(String n, Map<String,String> v){ + this.put(n,v); + } + + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + sb.append("{"); + boolean first=true; + for(Map.Entry<String, Map<String,String>> entry : this.entrySet()){ + if(!first)sb.append(","); + first=false; + sb.append(entry.getKey()+"->"+entry.getValue()); + } + sb.append("}"); + return sb.toString(); + } + } + + /** + * List of Name/Value + * + */ + public static class NameValueList extends ArrayList<NameValuePair> implements NameValueCollection, List<NameValuePair> { + private static final long serialVersionUID = 1808636823825029983L; + + public void addToCollection(String n, Map<String,String> v){ + this.add(new NameValuePair(n,v)); + } + @Override + public String toString(){ + StringBuffer sb = new StringBuffer(); + sb.append("{"); + boolean first = true; + for(NameValuePair nvp : this){ + if(!first)sb.append(","); + first=false; + sb.append(nvp.toString()); + } + sb.append("}"); + return sb.toString(); + } + } + + /** + * + * Splits a delimiter separated string, tolerating presence of non separator commas + * within double quoted segments. + * + * Eg. + * com.ibm.ws.eba.helloWorldService;version="[1.0.0, 1.0.0]" & + * com.ibm.ws.eba.helloWorldService;version="1.0.0" + * com.ibm.ws.eba.helloWorld;version="2";bundle-version="[2,30)" + * com.acme.foo;weirdAttr="one;two;three";weirdDir:="1;2;3" + * @param value the value to be split + * @param delimiter the delimiter string such as ',' etc. + * @return List<String> the components of the split String in a list + */ + public static List<String> split(String value, String delimiter) + { + return ManifestHeaderUtils.split(value, delimiter); + } + + + /** + * Internal method to parse headers with the format<p> + * [Name](;[Name])*(;[attribute-name]=[attribute-value])*<br> + * Eg.<br> + * rumplestiltskin;thing=value;other=something<br> + * littleredridinghood + * bundle1;bundle2;other=things + * bundle1;bundle2 + * + * @param s data to parse + * @return a list of NameValuePair, with the Name being the name component, + * and the Value being a NameValueMap of key->value mappings. + */ + private static List<NameValuePair> genericNameWithNameValuePairProcess(String s){ + String name; + Map<String,String> params = null; + List<NameValuePair> nameValues = new ArrayList<NameValuePair>(); + List<String> pkgs = new ArrayList<String>(); + int index = s.indexOf(";"); + if(index==-1){ + name = s; + params = new HashMap<String, String>(); + pkgs.add(name); + }else{ + name = s.substring(0,index).trim(); + String tail = s.substring(index+1).trim(); + + pkgs.add(name); // add the first package + StringBuilder parameters = new StringBuilder(); + + + // take into consideration of multiple packages separated by ';' + // while they share the same attributes or directives + List<String> tailParts = split(tail, ";"); + boolean firstParameter =false; + + for (String part : tailParts) { + // if it is not a parameter and no parameter appears in front of it, it must a package + if (!!!(part.contains("="))) { + // Need to make sure no parameter appears before the package, otherwise ignore this string + // as this syntax is invalid + if (!!!(firstParameter)) + pkgs.add(part); + } else { + if (!!!(firstParameter)) + firstParameter = true; + + parameters.append(part + ";"); + } + } + + if (parameters.length() != 0) { + //remove the final ';' if there is one + if (parameters.toString().endsWith(";")) { + + parameters = parameters.deleteCharAt(parameters.length() -1); + } + + params = genericNameValueProcess(parameters.toString()); + } + + } + for (String pkg : pkgs) { + nameValues.add(new NameValuePair(pkg,params)); + } + + return nameValues; + + } + + /** + * Internal method to parse headers with the format<p> + * [attribute-name]=[attribute-value](;[attribute-name]=[attribute-value])*<br> + * Eg.<br> + * thing=value;other=something<br> + * <p> + * Note. Directives (name:=value) are represented in the map with name suffixed by ':' + * + * @param s data to parse + * @return a NameValueMap, with attribute-name -> attribute-value. + */ + private static Map<String,String> genericNameValueProcess(String s){ + Map<String,String> params = new HashMap<String,String>(); + List<String> parameters = split(s, ";"); + for(String parameter : parameters) { + List<String> parts = split(parameter,"="); + // do a check, otherwise we might get NPE + if (parts.size() ==2) { + String second = parts.get(1).trim(); + if (second.startsWith("\"") && second.endsWith("\"")) + second = second.substring(1,second.length()-1); + + String first = parts.get(0).trim(); + + // make sure for directives we clear out any space as in "directive :=value" + if (first.endsWith(":")) { + first = first.substring(0, first.length()-1).trim()+":"; + } + + params.put(first, second); + } + } + + return params; + } + + /** + * Processes an import/export style header.. <p> + * pkg1;attrib=value;attrib=value,pkg2;attrib=value,pkg3;attrib=value + * + * @param out The collection to add each package name + attrib map to. + * @param s The data to parse + */ + private static void genericImportExportProcess(NameValueCollection out, String s){ + List<String> packages = split(s, ","); + for(String pkg : packages){ + List<NameValuePair> ps = genericNameWithNameValuePairProcess(pkg); + for (NameValuePair p : ps) { + out.addToCollection(p.getName(), p.getAttributes()); + } + } + } + + /** + * Parse an export style header.<p> + * pkg1;attrib=value;attrib=value,pkg2;attrib=value,pkg3;attrib=value2 + * <p> + * Result is returned as a list, as export does allow duplicate package exports. + * + * @param s The data to parse. + * @return List of NameValuePairs, where each Name in the list is an exported package, + * with its associated Value being a NameValueMap of any attributes declared. + */ + public static List<NameValuePair> parseExportString(String s){ + NameValueList retval = new NameValueList(); + genericImportExportProcess(retval, s); + return retval; + } + + /** + * Parse an export style header in a list.<p> + * pkg1;attrib=value;attrib=value + * pkg2;attrib=value + * pkg3;attrib=value2 + * <p> + * Result is returned as a list, as export does allow duplicate package exports. + * + * @param list The data to parse. + * @return List of NameValuePairs, where each Name in the list is an exported package, + * with its associated Value being a NameValueMap of any attributes declared. + */ + public static List<NameValuePair> parseExportList(List<String> list){ + NameValueList retval = new NameValueList(); + for(String pkg : list){ + List<NameValuePair> ps = genericNameWithNameValuePairProcess(pkg); + for (NameValuePair p : ps) { + retval.addToCollection(p.getName(), p.getAttributes()); + } + } + return retval; + } + + /** + * Parse an import style header.<p> + * pkg1;attrib=value;attrib=value,pkg2;attrib=value,pkg3;attrib=value + * <p> + * Result is returned as a set, as import does not allow duplicate package imports. + * + * @param s The data to parse. + * @return Map of NameValuePairs, where each Key in the Map is an imported package, + * with its associated Value being a NameValueMap of any attributes declared. + */ + public static Map<String, Map<String, String>> parseImportString(String s){ + NameValueMap retval = new NameValueMap(); + genericImportExportProcess(retval, s); + return retval; + } + + /** + * Parse a bundle symbolic name.<p> + * bundlesymbolicname;attrib=value;attrib=value + * <p> + * + * @param s The data to parse. + * @return NameValuePair with Name being the BundleSymbolicName, + * and Value being any attribs declared for the name. + */ + public static NameValuePair parseBundleSymbolicName(String s){ + return genericNameWithNameValuePairProcess(s).get(0); // should just return the first one + } + + /** + * Parse a version range.. + * + * @param s + * @return VersionRange object. + * @throws IllegalArgumentException if the String could not be parsed as a VersionRange + */ + public static VersionRange parseVersionRange(String s) throws IllegalArgumentException{ + return new VersionRange(s); + } + + /** + * Parse a version range and indicate if the version is an exact version + * + * @param s + * @param exactVersion + * @return VersionRange object. + * @throws IllegalArgumentException if the String could not be parsed as a VersionRange + */ + public static VersionRange parseVersionRange(String s, boolean exactVersion) throws IllegalArgumentException{ + return new VersionRange(s, exactVersion); + } + + /** + * Generate a filter from a set of attributes. This filter will be suitable + * for presentation to OBR This means that, due to the way OBR works, it + * will include a stanza of the form, (mandatory:<*mandatoryAttribute) + * Filter strings generated by this method will therefore tend to break the + * standard OSGi Filter class. The OBR stanza can be stripped out later if + * required. + * + * @param attribs + * @return filter string + */ + public static String generateFilter(Map<String, Object> attribs) { + StringBuilder filter = new StringBuilder("(&"); + boolean realAttrib = false; + StringBuffer realAttribs = new StringBuffer(); + + if (attribs == null) { + attribs = new HashMap<String, Object>(); + } + + for (Map.Entry<String, Object> attrib : attribs.entrySet()) { + String attribName = attrib.getKey(); + + if (attribName.endsWith(":")) { + // skip all directives. It is used to affect the attribs on the + // filter xml. + } else if ((Constants.VERSION_ATTRIBUTE.equals(attribName)) + || (Constants.BUNDLE_VERSION_ATTRIBUTE.equals(attribName))) { + // version and bundle-version attrib requires special + // conversion. + realAttrib = true; + + VersionRange vr = ManifestHeaderProcessor + .parseVersionRange(attrib.getValue().toString()); + + filter.append("(" + attribName + ">=" + vr.getMinimumVersion()); + + if (vr.getMaximumVersion() != null) { + filter.append(")(" + attribName + "<="); + filter.append(vr.getMaximumVersion()); + } + + if (vr.getMaximumVersion() != null && vr.isMinimumExclusive()) { + filter.append(")(!(" + attribName + "="); + filter.append(vr.getMinimumVersion()); + filter.append(")"); + } + + if (vr.getMaximumVersion() != null && vr.isMaximumExclusive()) { + filter.append(")(!(" + attribName + "="); + filter.append(vr.getMaximumVersion()); + filter.append(")"); + } + filter.append(")"); + + } else if (NESTED_FILTER_ATTRIBUTE.equals(attribName)) { + // Filters go in whole, no formatting needed + realAttrib = true; + filter.append(attrib.getValue()); + + } else if (Constants.OBJECTCLASS.equals(attribName)) { + realAttrib = true; + // objectClass has a "," separated list of interfaces + String[] values = attrib.getValue().toString().split(","); + for (String s : values) + filter.append("(" + Constants.OBJECTCLASS + "=" + s + ")"); + + } else { + // attribName was not version.. + realAttrib = true; + + filter.append("(" + attribName + "=" + attrib.getValue() + ")"); + // store all attributes in order to build up the mandatory + // filter and separate them with ", " + // skip bundle-symbolic-name in the mandatory directive query + if (!!!Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE + .equals(attribName)) { + realAttribs.append(attribName); + realAttribs.append(", "); + } + } + } +// /* +// * The following is how OBR makes mandatory attributes work, we require +// * that the set of mandatory attributes on the export is a subset of (or +// * equal to) the set of the attributes we supply. +// */ +// +// if (realAttribs.length() > 0) { +// String attribStr = (realAttribs.toString()).trim(); +// // remove the final , +// if ((attribStr.length() > 0) && (attribStr.endsWith(","))) { +// attribStr = attribStr.substring(0, attribStr.length() - 1); +// } +// // build the mandatory filter, e.g.(mandatory:<*company, local) +// filter.append("(" + Constants.MANDATORY_DIRECTIVE + ":" + "<*" +// + attribStr + ")"); +// } + + // Prune (& off the front and ) off end + String filterString = filter.toString(); + int openBraces = 0; + for (int i = 0; openBraces < 3; i++) { + i = filterString.indexOf('(', i); + if (i == -1) { + break; + } else { + openBraces++; + } + } + if (openBraces < 3 && filterString.length() > 2) { + filter.delete(0, 2); + } else { + filter.append(")"); + } + + String result = ""; + if (realAttrib != false) { + result = filter.toString(); + } + return result; + } + + /** + * Generate a filter from a set of attributes. This filter will be suitable + * for presentation to OBR. This means that, due to the way OBR works, it will + * include a stanza of the form, (mandatory:<*mandatoryAttribute) Filter + * strings generated by this method will therefore tend to break the standard + * OSGi Filter class. The OBR stanza can be stripped out later if required. + * + * We may wish to consider relocating this method since VersionRange has its + * own top level class. + * + * @param type + * @param name + * @param attribs + * @return filter string + */ + public static String generateFilter(String type, String name, + Map<String, Object> attribs) { + StringBuffer filter = new StringBuffer(); + String result; + // shortcut for the simple case with no attribs. + + if (attribs == null || attribs.isEmpty()) + filter.append("(" + type + "=" + name + ")"); + else { + // process all the attribs passed. + // find out whether there are attributes on the filter + + filter.append("(&(" + type + "=" + name + ")"); + + String filterString = generateFilter(attribs); + + int start = 0; + int end = filterString.length(); + if (filterString.startsWith("(&")) { + start = 2; + end--; + } + + if ("".equals(filterString)) { + filter.delete(0, 2); + } else { + filter.append(filterString, start, end); + filter.append(")"); + } + } + + result = filter.toString(); + + return result; + } + + private static Map<String, String> parseFilterList(String filter) { + + Map<String, String> result = new HashMap<String, String>(); + Set<String> negatedVersions = new HashSet<String>(); + Set<String> negatedBundleVersions = new HashSet<String>(); + + String lowerVersion = null; + String upperVersion = null; + String lowerBundleVersion = null; + String upperBundleVersion = null; + + Matcher m = FILTER_ATTR.matcher(filter); + while (m.find()) { + boolean negation = m.group(1) != null; + String attr = m.group(2); + String op = m.group(3); + String value = m.group(4); + + if (Constants.VERSION_ATTRIBUTE.equals(attr)) { + if (negation) { + negatedVersions.add(value); + } else { + if (GREATER_EQ_OP.equals(op)) + lowerVersion = value; + else if (LESS_EQ_OP.equals(op)) + upperVersion = value; + else + throw new IllegalArgumentException(); + } + } else if (Constants.BUNDLE_VERSION_ATTRIBUTE.equals(attr)) { + // bundle-version is like version, but may be specified at the + // same time + // therefore we have similar code with separate variables + if (negation) { + negatedBundleVersions.add(value); + } else { + if (GREATER_EQ_OP.equals(op)) + lowerBundleVersion = value; + else if (LESS_EQ_OP.equals(op)) + upperBundleVersion = value; + else + throw new IllegalArgumentException(); + } + } else { + result.put(attr, value); + } + } + + if (lowerVersion != null) { + StringBuilder versionAttr = new StringBuilder(lowerVersion); + if (upperVersion != null) { + versionAttr.append(",").append(upperVersion).insert(0, + negatedVersions.contains(lowerVersion) ? '(' : '[').append( + negatedVersions.contains(upperVersion) ? ')' : ']'); + } + + result.put(Constants.VERSION_ATTRIBUTE, versionAttr.toString()); + } + // Do it again for bundle-version + if (lowerBundleVersion != null) { + StringBuilder versionAttr = new StringBuilder(lowerBundleVersion); + if (upperBundleVersion != null) { + versionAttr.append(",").append(upperBundleVersion).insert(0, + negatedBundleVersions.contains(lowerBundleVersion) ? '(' : '[') + .append( + negatedBundleVersions.contains(upperBundleVersion) ? ')' : ']'); + } + + result.put(Constants.BUNDLE_VERSION_ATTRIBUTE, versionAttr.toString()); + } + + return result; + } + + public static Map<String,String> parseFilter(String filter) + { + Map<String,String> result; + if (filter.startsWith("(&")) { + result = parseFilterList(filter.substring(2, filter.length()-1)); + } else { + result = parseFilterList(filter); + } + return result; + } + +} +
http://git-wip-us.apache.org/repos/asf/karaf/blob/1bcdb173/region/src/main/java/org/apache/karaf/region/persist/internal/util/ManifestHeaderUtils.java ---------------------------------------------------------------------- diff --git a/region/src/main/java/org/apache/karaf/region/persist/internal/util/ManifestHeaderUtils.java b/region/src/main/java/org/apache/karaf/region/persist/internal/util/ManifestHeaderUtils.java new file mode 100644 index 0000000..8c87616 --- /dev/null +++ b/region/src/main/java/org/apache/karaf/region/persist/internal/util/ManifestHeaderUtils.java @@ -0,0 +1,85 @@ +/* + * 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.karaf.region.persist.internal.util; + +import java.util.ArrayList; +import java.util.List; + +public class ManifestHeaderUtils { + + /** + * + * Splits a delimiter separated string, tolerating presence of non separator commas + * within double quoted segments. + * + * Eg. + * com.ibm.ws.eba.helloWorldService;version="[1.0.0, 1.0.0]" & + * com.ibm.ws.eba.helloWorldService;version="1.0.0" + * com.ibm.ws.eba.helloWorld;version="2";bundle-version="[2,30)" + * com.acme.foo;weirdAttr="one;two;three";weirdDir:="1;2;3" + * @param value the value to be split + * @param delimiter the delimiter string such as ',' etc. + * @return List<String> the components of the split String in a list + */ + public static List<String> split(String value, String delimiter) + { + List<String> result = new ArrayList<String>(); + if (value != null) { + String[] packages = value.split(delimiter); + + for (int i = 0; i < packages.length; ) { + String tmp = packages[i++].trim(); + // if there is a odd number of " in a string, we need to append + while (count(tmp, "\"") % 2 != 0) { + // check to see if we need to append the next package[i++] + if (i<packages.length) + tmp = tmp + delimiter + packages[i++].trim(); + else + // oops. The double quotes are not paired up. We have reached to the end of the string. + throw new IllegalArgumentException("Unmatched double quotes: " + tmp); + } + + result.add(tmp); + + } + } + return result; + } + + /** + * count the number of characters in a string + * @param parent The string to be searched + * @param subString The substring to be found + * @return the number of occurrence of the subString + */ + private static int count(String parent, String subString) { + + int count = 0 ; + int i = parent.indexOf(subString); + while (i > -1) { + if (parent.length() >= i+1) + parent = parent.substring(i+1); + count ++; + i = parent.indexOf(subString); + } + return count; + } +} http://git-wip-us.apache.org/repos/asf/karaf/blob/1bcdb173/region/src/main/java/org/apache/karaf/region/persist/internal/util/VersionRange.java ---------------------------------------------------------------------- diff --git a/region/src/main/java/org/apache/karaf/region/persist/internal/util/VersionRange.java b/region/src/main/java/org/apache/karaf/region/persist/internal/util/VersionRange.java new file mode 100644 index 0000000..19bbc77 --- /dev/null +++ b/region/src/main/java/org/apache/karaf/region/persist/internal/util/VersionRange.java @@ -0,0 +1,456 @@ +/* + * 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.karaf.region.persist.internal.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.osgi.framework.Version; + +public final class VersionRange { + + /** A string representation of the version. */ + private String version; + + /** The minimum desired version for the bundle */ + private Version minimumVersion; + + /** The maximum desired version for the bundle */ + private Version maximumVersion; + + /** True if the match is exclusive of the minimum version */ + private boolean minimumExclusive; + + /** True if the match is exclusive of the maximum version */ + private boolean maximumExclusive; + + /** A regexp to select the version */ + private static final Pattern versionCapture = Pattern.compile("\"?(.*?)\"?$"); + + /** + * + * @param version + * version for the verioninfo + */ + public VersionRange(String version) { + this.version = version; + processVersionAttribute(version); + } + + /** + * This method should be used to create a version range from a single + * version string. + * @param version + * version for the versioninfo + * @param exactVersion + * whether this is an exact version {@code true} or goes to infinity + * {@code false} + */ + public VersionRange(String version, boolean exactVersion) { + + if (exactVersion) { + // Do not store this string as it might be just a version, or a range! + processExactVersionAttribute(version); + } else { + this.version = version; + processVersionAttribute(this.version); + } + + assertInvariants(); + } + + /** + * Constructor designed for internal use only. + * + * @param maximumVersion + * @param maximumExclusive + * @param minimumVersion + * @param minimumExclusive + * @throws IllegalArgumentException + * if parameters are not valid. + */ + private VersionRange(Version maximumVersion, + boolean maximumExclusive, + Version minimumVersion, + boolean minimumExclusive) { + this.maximumVersion = maximumVersion; + this.maximumExclusive = maximumExclusive; + this.minimumVersion = minimumVersion; + this.minimumExclusive = minimumExclusive; + + assertInvariants(); + } + + /* + * (non-Javadoc) + * + * @see org.apache.aries.application.impl.VersionRange#toString() + */ + @Override + public String toString() { + // Some constructors don't take in a string that we can return directly, + // so construct one if needed + if (version == null) { + if (maximumVersion == null) { + version = minimumVersion.toString(); + } else { + version = (minimumExclusive ? "(" : "[") + minimumVersion + "," + maximumVersion + + (maximumExclusive ? ")" : "]"); + } + } + return this.version; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + minimumVersion.hashCode(); + result = 31 * result + (minimumExclusive ? 1 : 0); + result = 31 * result + (maximumVersion != null ? maximumVersion.hashCode() : 0); + result = 31 * result + (maximumExclusive ? 1 : 0); + return result; + } + + @Override + public boolean equals(Object other) { + boolean result = false; + if (this == other) { + result = true; + } else if (other instanceof VersionRange) { + VersionRange vr = (VersionRange) other; + result = minimumVersion.equals(vr.minimumVersion) + && minimumExclusive == vr.minimumExclusive + && (maximumVersion == null ? vr.maximumVersion == null : maximumVersion + .equals(vr.maximumVersion)) && maximumExclusive == vr.maximumExclusive; + } + + return result; + } + + /** + * this method returns the exact version from the versionInfo obj. + * this is used for DeploymentContent only to return a valid exact version + * otherwise, null is returned. + * @return the exact version + */ + public Version getExactVersion() { + Version v = null; + if (isExactVersion()) { + v = getMinimumVersion(); + } + return v; + } + + /** + * get the maximum version + * @return the maximum version + */ + public Version getMaximumVersion() { + return maximumVersion; + } + + /** + * get the minimum version + * @return the minimum version + */ + public Version getMinimumVersion() { + return minimumVersion; + } + + /** + * is the maximum version exclusive + * @return is the max version in the range. + */ + public boolean isMaximumExclusive() { + return maximumExclusive; + } + + /** + * is the maximum version unbounded + * @return true if no upper bound was specified. + */ + public boolean isMaximumUnbounded() { + boolean unbounded = maximumVersion == null; + return unbounded; + } + + /** + * is the minimum version exclusive + * @return true if the min version is in range. + */ + public boolean isMinimumExclusive() { + return minimumExclusive; + } + + /** + * this is designed for deployed-version as that is the exact version. + * + * @param version + * @return + * @throws IllegalArgumentException + */ + private boolean processExactVersionAttribute(String version) throws IllegalArgumentException { + boolean success = processVersionAttribute(version); + + if (maximumVersion == null) { + maximumVersion = minimumVersion; + } + + if (!minimumVersion.equals(maximumVersion)) { + throw new IllegalArgumentException("Version is not exact: " + version); + } + + if (!!!isExactVersion()) { + throw new IllegalArgumentException("Version is not exact: " + version); + } + + return success; + } + + /** + * process the version attribute, + * + * @param version + * the value to be processed + * @return + * @throws IllegalArgumentException + */ + private boolean processVersionAttribute(String version) throws IllegalArgumentException { + boolean success = false; + + if (version == null) { + throw new IllegalArgumentException("Version is null"); + } + + Matcher matches = versionCapture.matcher(version); + + if (matches.matches()) { + String versions = matches.group(1); + + if ((versions.startsWith("[") || versions.startsWith("(")) + && (versions.endsWith("]") || versions.endsWith(")"))) { + if (versions.startsWith("[")) + minimumExclusive = false; + else if (versions.startsWith("(")) + minimumExclusive = true; + + if (versions.endsWith("]")) + maximumExclusive = false; + else if (versions.endsWith(")")) + maximumExclusive = true; + + int index = versions.indexOf(','); + String minVersion = versions.substring(1, index); + String maxVersion = versions.substring(index + 1, versions.length() - 1); + + try { + minimumVersion = new Version(minVersion.trim()); + maximumVersion = new Version(maxVersion.trim()); + success = true; + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("Version cannot be decoded: " + version, nfe); + } + } else { + try { + if (versions.trim().length() == 0) + minimumVersion = new Version(0, 0, 0); + else + minimumVersion = new Version(versions.trim()); + success = true; + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("Version cannot be decoded: " + version, nfe); + } + } + } else { + throw new IllegalArgumentException("Version cannot be decoded: " + version); + } + + return success; + } + + /** + * Assert object invariants. Called by constructors to verify that arguments + * were valid. + * + * @throws IllegalArgumentException + * if invariants are violated. + */ + private void assertInvariants() { + if (minimumVersion == null + || !isRangeValid(minimumVersion, minimumExclusive, maximumVersion, maximumExclusive)) { + IllegalArgumentException e = new IllegalArgumentException(); + throw e; + } + } + + /** + * Check if the supplied parameters describe a valid version range. + * + * @param min + * the minimum version. + * @param minExclusive + * whether the minimum version is exclusive. + * @param max + * the maximum version. + * @param maxExclusive + * whether the maximum version is exclusive. + * @return true is the range is valid; otherwise false. + */ + private boolean isRangeValid(Version min, + boolean minExclusive, + Version max, + boolean maxExclusive) { + boolean result; + + // A null maximum version is unbounded so means that minimum is smaller + // than + // maximum. + int minMaxCompare = (max == null ? -1 : min.compareTo(max)); + if (minMaxCompare > 0) { + // Minimum larger than maximum is invalid. + result = false; + } else if (minMaxCompare == 0 && (minExclusive || maxExclusive)) { + // If min and max are the same, and either are exclusive, no valid + // range + // exists. + result = false; + } else { + // Range is valid. + result = true; + } + + return result; + } + + /** + * This method checks that the provided version matches the desired version. + * + * @param version + * the version. + * @return true if the version matches, false otherwise. + */ + public boolean matches(Version version) { + boolean result; + if (this.getMaximumVersion() == null) { + result = this.getMinimumVersion().compareTo(version) <= 0; + } else { + int minN = this.isMinimumExclusive() ? 0 : 1; + int maxN = this.isMaximumExclusive() ? 0 : 1; + + result = (this.getMinimumVersion().compareTo(version) < minN) + && (version.compareTo(this.getMaximumVersion()) < maxN); + } + return result; + } + + /** + * check if the versioninfo is the exact version + * @return true if the range will match 1 exact version. + */ + public boolean isExactVersion() { + return minimumVersion.equals(maximumVersion) && minimumExclusive == maximumExclusive + && !!!minimumExclusive; + } + + /** + * Create a new version range that is the intersection of {@code this} and the argument. + * In other words, the largest version range that lies within both {@code this} and + * the parameter. + * @param r a version range to be intersected with {@code this}. + * @return a new version range, or {@code null} if no intersection is possible. + */ + public VersionRange intersect(VersionRange r) { + // Use the highest minimum version. + final Version newMinimumVersion; + final boolean newMinimumExclusive; + int minCompare = minimumVersion.compareTo(r.getMinimumVersion()); + if (minCompare > 0) { + newMinimumVersion = minimumVersion; + newMinimumExclusive = minimumExclusive; + } else if (minCompare < 0) { + newMinimumVersion = r.getMinimumVersion(); + newMinimumExclusive = r.isMinimumExclusive(); + } else { + newMinimumVersion = minimumVersion; + newMinimumExclusive = (minimumExclusive || r.isMinimumExclusive()); + } + + // Use the lowest maximum version. + final Version newMaximumVersion; + final boolean newMaximumExclusive; + // null maximum version means unbounded, so the highest possible value. + if (maximumVersion == null) { + newMaximumVersion = r.getMaximumVersion(); + newMaximumExclusive = r.isMaximumExclusive(); + } else if (r.getMaximumVersion() == null) { + newMaximumVersion = maximumVersion; + newMaximumExclusive = maximumExclusive; + } else { + int maxCompare = maximumVersion.compareTo(r.getMaximumVersion()); + if (maxCompare < 0) { + newMaximumVersion = maximumVersion; + newMaximumExclusive = maximumExclusive; + } else if (maxCompare > 0) { + newMaximumVersion = r.getMaximumVersion(); + newMaximumExclusive = r.isMaximumExclusive(); + } else { + newMaximumVersion = maximumVersion; + newMaximumExclusive = (maximumExclusive || r.isMaximumExclusive()); + } + } + + VersionRange result; + if (isRangeValid(newMinimumVersion, newMinimumExclusive, newMaximumVersion, + newMaximumExclusive)) { + result = new VersionRange(newMaximumVersion, newMaximumExclusive, newMinimumVersion, + newMinimumExclusive); + } else { + result = null; + } + return result; + } + + /** + * Parse a version range.. + * + * @param s + * @return VersionRange object. + * @throws IllegalArgumentException + * if the String could not be parsed as a VersionRange + */ + public static VersionRange parseVersionRange(String s) throws IllegalArgumentException { + return new VersionRange(s); + } + + /** + * Parse a version range and indicate if the version is an exact version + * + * @param s + * @param exactVersion + * @return VersionRange object. + * @throws IllegalArgumentException + * if the String could not be parsed as a VersionRange + */ + public static VersionRange parseVersionRange(String s, boolean exactVersion) + throws IllegalArgumentException { + return new VersionRange(s, exactVersion); + } +} http://git-wip-us.apache.org/repos/asf/karaf/blob/1bcdb173/region/src/main/resources/org/apache/karaf/region/persist/region.xsd ---------------------------------------------------------------------- diff --git a/region/src/main/resources/org/apache/karaf/region/persist/region.xsd b/region/src/main/resources/org/apache/karaf/region/persist/region.xsd new file mode 100644 index 0000000..8ca26e7 --- /dev/null +++ b/region/src/main/resources/org/apache/karaf/region/persist/region.xsd @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> + +<!-- $Rev$ $Date$ --> + +<xsd:schema xmlns="http://karaf.apache.org/xmlns/region/v1.0.0" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + targetNamespace="http://karaf.apache.org/xmlns/region/v1.0.0" + elementFormDefault="qualified" + attributeFormDefault="unqualified"> + + + <xsd:annotation> + <xsd:documentation> + Defines the configuration elements for Apache Karaf region xml configuration. + </xsd:documentation> + </xsd:annotation> + + <xsd:element name="regions" type="regionsType"/> + + + <xsd:complexType name="regionsType"> + <xsd:annotation> + <xsd:documentation> + Regions element + </xsd:documentation> + </xsd:annotation> + <xsd:sequence> + <xsd:element name="region" type="regionType" minOccurs="0" maxOccurs="unbounded"/> + <xsd:element name="filter" type="filterType" minOccurs="0" maxOccurs="unbounded"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="regionType"> + <xsd:annotation> + <xsd:documentation> + Region element + </xsd:documentation> + </xsd:annotation> + <xsd:sequence> + <xsd:element name="bundle" type="regionBundleType" minOccurs="0" maxOccurs="unbounded"/> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required"/> + </xsd:complexType> + + + <xsd:complexType name="regionBundleType"> + <xsd:sequence/> + <xsd:attribute name="id" type="xsd:long"/> + <xsd:attribute name="location" type="xsd:string"/> + </xsd:complexType> + + <xsd:complexType name="filterBundleType"> + <xsd:sequence> + <xsd:element name="attribute" type="filterAttributeType" minOccurs="0" maxOccurs="unbounded"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:long"/> + <xsd:attribute name="symbolic-name" type="xsd:string"/> + <xsd:attribute name="version" type="xsd:string"/> + </xsd:complexType> + + <xsd:complexType name="filterType"> + <xsd:sequence> + <xsd:element name="bundle" type="filterBundleType" minOccurs="0" maxOccurs="unbounded"/> + <xsd:element name="package" type="filterPackageType" minOccurs="0" maxOccurs="unbounded"/> + <xsd:element name="namespace" type="filterNamespaceType" minOccurs="0" maxOccurs="unbounded"/> + </xsd:sequence> + <xsd:attribute name="from" type="xsd:string" use="required"/> + <xsd:attribute name="to" type="xsd:string" use="required"/> + </xsd:complexType> + + <xsd:complexType name="filterPackageType"> + <xsd:sequence> + <xsd:element name="attribute" type="filterAttributeType" minOccurs="0" maxOccurs="unbounded"/> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required"/> + <xsd:attribute name="version" type="xsd:string"/> + </xsd:complexType> + + <xsd:complexType name="filterAttributeType"> + <xsd:sequence/> + <xsd:attribute name="name" type="xsd:string" use="required"/> + <xsd:attribute name="value" type="xsd:string" use="required"/> + </xsd:complexType> + + <xsd:complexType name="filterNamespaceType"> + <xsd:sequence> + <xsd:element name="attribute" type="filterAttributeType" minOccurs="0" maxOccurs="unbounded"/> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required"/> + </xsd:complexType> +</xsd:schema>