+ * A factory constructor that converts a stringified object (as output by
+ * toStableString) to an object instance.
+ *
+ * Format:
+ * <ul>
+ * <li>(condition1) AND (condition2) AND (condition3)</li>
+ * <li>or</li>
+ * <li>(condition1) OR (condition2) OR (condition3)</li>
+ * </ul>
+ * where OR and AND are not case-sensitive, and may not be mixed. Brackets are
+ * optional if there is only one condition.
+ *
+ * @param descriptor
+ * @return
+ * @see FeatureMatcher#fromString(String)
+ */
+ public static FeatureMatcherSet fromString(final String descriptor)
+ {
+ String invalid = "Invalid descriptor: " + descriptor;
+ boolean firstCondition = true;
+ FeatureMatcherSet result = new FeatureMatcherSet();
+
+ String leftToParse = descriptor.trim();
+
+ while (leftToParse.length() > 0)
+ {
+ /*
+ * inspect AND or OR condition, check not mixed
+ */
+ boolean and = true;
+ if (!firstCondition)
+ {
+ int spacePos = leftToParse.indexOf(SPACE);
+ if (spacePos == -1)
+ {
+ // trailing junk after a match condition
+ System.err.println(invalid);
+ return null;
+ }
+ String conjunction = leftToParse.substring(0, spacePos);
+ leftToParse = leftToParse.substring(spacePos + 1).trim();
+ if (conjunction.equalsIgnoreCase(AND))
+ {
+ and = true;
+ }
+ else if (conjunction.equalsIgnoreCase(OR))
+ {
+ and = false;
+ }
+ else
+ {
+ // not an AND or an OR - invalid
+ System.err.println(invalid);
+ return null;
+ }
+ }
+
+ /*
+ * now extract the next condition and AND or OR it
+ */
+ String nextCondition = leftToParse;
+ if (leftToParse.startsWith(OPEN_BRACKET))
+ {
+ int closePos = leftToParse.indexOf(CLOSE_BRACKET);
+ if (closePos == -1)
+ {
+ System.err.println(invalid);
+ return null;
+ }
+ nextCondition = leftToParse.substring(1, closePos);
+ leftToParse = leftToParse.substring(closePos + 1).trim();
+ }
+ else
+ {
+ leftToParse = "";
+ }
+
+ FeatureMatcher fm = FeatureMatcher.fromString(nextCondition);
+ if (fm == null)
+ {
+ System.err.println(invalid);
+ return null;
+ }
+ try
+ {
+ if (and)
+ {
+ result.and(fm);
+ }
+ else
+ {
+ result.or(fm);
+ }
+ firstCondition = false;
+ } catch (IllegalStateException e)
+ {
+ // thrown if OR and AND are mixed
+ System.err.println(invalid);
+ return null;
+ }
+
+ }
+ return result;
+ }
+
+ /**