--- /dev/null
+package org.simantics.modeling.services;\r
+\r
+import java.nio.CharBuffer;\r
+import java.util.Comparator;\r
+import java.util.Formatter;\r
+import java.util.List;\r
+import java.util.Locale;\r
+import java.util.Set;\r
+import java.util.TreeSet;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.exception.DatabaseException;\r
+\r
+/**\r
+ * An abstract base class that contains some helpers for implementing your own\r
+ * {@link ComponentNamingStrategy}. A default implementation is also provided\r
+ * for {@link #findFreshInstanceName(ReadGraph, Resource, Resource, Resource)}\r
+ * based on the Layer0 ontology <code>HasGeneratedNamePrefix</code> property.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+public abstract class ComponentNamingStrategyBase implements ComponentNamingStrategy {\r
+\r
+ protected static final boolean DEBUG_ALL = false;\r
+ protected static final boolean DEBUG_NAME_MATCHING = false | DEBUG_ALL;\r
+\r
+ protected final String generatedNameFormat;\r
+ protected final boolean caseInsensitive;\r
+\r
+ /**\r
+ * Base constructor for a naming strategy with the specified format as the\r
+ * generated name format. The format will receive two arguments:\r
+ * <ol>\r
+ * <li>proposed name as {@link String}</li>\r
+ * <li>an {@link Integer} that attempts to make the name unique within a\r
+ * context</li>\r
+ * </ol>\r
+ * The simplest format specification utilizing both values is\r
+ * <code>"%s %d"</code>, producing "FOO 1". Another example of a useful name\r
+ * format is <code>"%s%04d"</code>, producing "FOO0001". Reordering the\r
+ * arguments is also possible, e.g. <code>"%2$03d %1$s"</code>, producing\r
+ * "001 FOO".\r
+ * \r
+ * <p>\r
+ * See {@link Formatter} for the format specification and how to customize\r
+ * it.\r
+ * \r
+ * <p>\r
+ * This constructor will create a case-insensitive naming strategy.\r
+ * \r
+ * @param generatedNameFormat the format to use for generated names\r
+ */\r
+ public ComponentNamingStrategyBase(String generatedNameFormat) {\r
+ this(generatedNameFormat, true);\r
+ }\r
+\r
+ /**\r
+ * Base constructor for a naming strategy with the specified format as the\r
+ * generated name format. The format will receive two arguments:\r
+ * <ol>\r
+ * <li>proposed name as {@link String}</li>\r
+ * <li>an {@link Integer} that attempts to make the name unique within a\r
+ * context</li>\r
+ * </ol>\r
+ * The simplest format specification utilizing both values is\r
+ * <code>"%s %d"</code>, producing "FOO 1". Another example of a useful name\r
+ * format is <code>"%s%04d"</code>, producing "FOO0001". Reordering the\r
+ * arguments is also possible, e.g. <code>"%2$03d %1$s"</code>, producing\r
+ * "001 FOO".\r
+ * \r
+ * <p>\r
+ * See {@link Formatter} for the format specification and how to customize\r
+ * it.\r
+ * \r
+ * @param generatedNameFormat the format to use for generated names\r
+ * @param caseInsensitive controls whether the strategy shall be case-insensitive or not\r
+ */\r
+ public ComponentNamingStrategyBase(String generatedNameFormat, boolean caseInsensitive) {\r
+ this.generatedNameFormat = generatedNameFormat;\r
+ this.caseInsensitive = caseInsensitive;\r
+ }\r
+\r
+ @Override\r
+ public String findFreshInstanceName(ReadGraph graph, Resource configurationRoot, Resource container,\r
+ Resource componentType) throws NamingException, DatabaseException {\r
+ String proposition = ComponentNamingUtil.generateProposition(graph, container, componentType);\r
+ return validateInstanceName(graph, configurationRoot, container, componentType, proposition);\r
+ }\r
+\r
+ @Override\r
+ public String validateInstanceName(ReadGraph graph, Resource configurationRoot, Resource container,\r
+ Resource componentType, String proposition) throws NamingException, DatabaseException {\r
+ return validateInstanceName(graph, configurationRoot, container, componentType, proposition, false);\r
+ }\r
+\r
+ @Override\r
+ public List<String> validateInstanceNames(\r
+ ReadGraph graph,\r
+ Resource configurationRoot,\r
+ List<String> propositions,\r
+ boolean acceptProposition,\r
+ Set<String> externallyReserved)\r
+ throws NamingException, DatabaseException\r
+ {\r
+ throw new UnsupportedOperationException();\r
+ }\r
+\r
+ protected Comparator<Object> getComparator() {\r
+ return getComparator(caseInsensitive);\r
+ }\r
+\r
+ protected static Comparator<Object> getComparator(boolean caseInsensitive) {\r
+ return caseInsensitive ? CASE_INSENSITIVE_STRING_CHARBUFFER_COMPARATOR : CASE_SENSITIVE_STRING_CHARBUFFER_COMPARATOR;\r
+ }\r
+\r
+ protected static final Comparator<Object> CASE_SENSITIVE_STRING_CHARBUFFER_COMPARATOR = new Comparator<Object>() {\r
+ @Override\r
+ public int compare(Object o1, Object o2) {\r
+ String s1 = null;\r
+ String s2 = null;\r
+ if (o1 instanceof String)\r
+ s1 = (String) o1;\r
+ if (o2 instanceof String)\r
+ s2 = (String) o2;\r
+ if (s1 != null && s2 != null)\r
+ return s1.compareTo((String) o2);\r
+\r
+ if (s1 == null && s2 == null)\r
+ return 0;\r
+\r
+ if (s1 == null && (o1 instanceof CharBuffer))\r
+ return -compare(s2, (CharBuffer) o1);\r
+ if (s2 == null && (o2 instanceof CharBuffer))\r
+ return compare(s1, (CharBuffer) o2);\r
+\r
+ return 0;\r
+ }\r
+ int compare(String s, CharBuffer buf) {\r
+ int len1 = s.length();\r
+ int len2 = buf.position();\r
+ int n = Math.min(len1, len2);\r
+ int k = 0;\r
+ while (k < n) {\r
+ char c1 = s.charAt(k);\r
+ char c2 = buf.get(k);\r
+ if (c1 != c2) {\r
+ return c1 - c2;\r
+ }\r
+ k++;\r
+ }\r
+ return len1 - len2;\r
+ }\r
+ };\r
+\r
+ protected static final Comparator<Object> CASE_INSENSITIVE_STRING_CHARBUFFER_COMPARATOR = new Comparator<Object>() {\r
+ @Override\r
+ public int compare(Object o1, Object o2) {\r
+ String s1 = null;\r
+ String s2 = null;\r
+ if (o1 instanceof String)\r
+ s1 = (String) o1;\r
+ if (o2 instanceof String)\r
+ s2 = (String) o2;\r
+ if (s1 != null && s2 != null)\r
+ return s1.compareToIgnoreCase((String) o2);\r
+\r
+ if (s1 == null && s2 == null)\r
+ return 0;\r
+\r
+ if (s1 == null && (o1 instanceof CharBuffer))\r
+ return -compare(s2, (CharBuffer) o1);\r
+ if (s2 == null && (o2 instanceof CharBuffer))\r
+ return compare(s1, (CharBuffer) o2);\r
+\r
+ return 0;\r
+ }\r
+ int compare(String s, CharBuffer buf) {\r
+ int len1 = s.length();\r
+ int len2 = buf.position();\r
+ int n = Math.min(len1, len2);\r
+ int k = 0;\r
+ while (k < n) {\r
+ char c1 = s.charAt(k);\r
+ char c2 = buf.get(k);\r
+ if (c1 != c2) {\r
+ c1 = Character.toUpperCase(c1);\r
+ c2 = Character.toUpperCase(c2);\r
+ if (c1 != c2) {\r
+ c1 = Character.toLowerCase(c1);\r
+ c2 = Character.toLowerCase(c2);\r
+ if (c1 != c2) {\r
+ return c1 - c2;\r
+ }\r
+ }\r
+ }\r
+ k++;\r
+ }\r
+ return len1 - len2;\r
+ }\r
+ };\r
+\r
+ protected Set<String> findStartsWithMatches(Set<String> matchUniverse, String proposition) {\r
+ Set<String> result = new TreeSet<String>(getComparator());\r
+ if (matchUniverse.isEmpty())\r
+ return result;\r
+\r
+ for (String name : matchUniverse)\r
+ if (name.startsWith(proposition))\r
+ result.add(name);\r
+ return result;\r
+ }\r
+\r
+ protected Set<String> findStartsWithMatches(Set<String> matchUniverse, String proposition, Set<String> result) {\r
+ if (matchUniverse.isEmpty())\r
+ return result;\r
+\r
+ for (String name : matchUniverse)\r
+ if (name.startsWith(proposition))\r
+ result.add(name);\r
+ return result;\r
+ }\r
+\r
+ protected String findFreshName(Set<String> allReservedNames, Set<String> allRequestedNames, String proposition, boolean acceptProposition) {\r
+ if (allReservedNames == null)\r
+ throw new NullPointerException("null reserved name set");\r
+ if (proposition == null)\r
+ throw new NullPointerException("null proposition");\r
+\r
+ Set<String> startsWithMatches = findStartsWithMatches(allReservedNames, proposition);\r
+ if (allRequestedNames != null)\r
+ findStartsWithMatches(allRequestedNames, proposition, startsWithMatches);\r
+\r
+ return findFreshName(startsWithMatches, proposition, acceptProposition);\r
+ }\r
+\r
+ protected String findFreshName(Set<String> reserved, String proposition, boolean acceptProposition) {\r
+ return findFreshName(reserved, proposition, acceptProposition, generatedNameFormat);\r
+ }\r
+\r
+ protected String findFreshName(Set<String> reserved, String proposition, boolean acceptProposition, String nameFormat) {\r
+ if (proposition == null)\r
+ throw new NullPointerException("null proposition");\r
+\r
+ if (DEBUG_NAME_MATCHING)\r
+ System.out.println("Finding fresh proposed name '" + proposition + "' among matching reservations: " + reserved);\r
+\r
+ if (acceptProposition && !reserved.contains(proposition))\r
+ return proposition;\r
+\r
+ // Trying to optimize away unnecessary allocation of new String instances\r
+ // when looking for fresh names for objects.\r
+ CharBuffer cb = CharBuffer.allocate(proposition.length() + 10);\r
+ cb.mark();\r
+\r
+ @SuppressWarnings("resource")\r
+ Formatter formatter = new Formatter(cb, Locale.US);\r
+\r
+ //int i = reserved.size() + 1;\r
+ int i = 1;\r
+ for (;; ++i) {\r
+ cb.reset();\r
+ formatter.format(nameFormat, proposition, i);\r
+ if (!reserved.contains(cb)) {\r
+ // Construct a String from the CharBuffer\r
+ cb.limit(cb.position());\r
+ cb.rewind();\r
+ String result = cb.toString();\r
+ if (DEBUG_NAME_MATCHING)\r
+ System.out.println("\tfound fresh name: " + result);\r
+ return result;\r
+ }\r
+ }\r
+ }\r
+\r
+}
\ No newline at end of file