package org.simantics.modeling.services; import java.nio.CharBuffer; import java.util.Comparator; import java.util.Formatter; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.TreeSet; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.exception.DatabaseException; /** * An abstract base class that contains some helpers for implementing your own * {@link ComponentNamingStrategy}. A default implementation is also provided * for {@link #findFreshInstanceName(ReadGraph, Resource, Resource, Resource)} * based on the Layer0 ontology HasGeneratedNamePrefix property. * * @author Tuukka Lehtonen */ public abstract class ComponentNamingStrategyBase implements ComponentNamingStrategy { protected static final boolean DEBUG_ALL = false; protected static final boolean DEBUG_NAME_MATCHING = false | DEBUG_ALL; protected final String generatedNameFormat; protected final boolean caseInsensitive; /** * Base constructor for a naming strategy with the specified format as the * generated name format. The format will receive two arguments: *
    *
  1. proposed name as {@link String}
  2. *
  3. an {@link Integer} that attempts to make the name unique within a * context
  4. *
* The simplest format specification utilizing both values is * "%s %d", producing "FOO 1". Another example of a useful name * format is "%s%04d", producing "FOO0001". Reordering the * arguments is also possible, e.g. "%2$03d %1$s", producing * "001 FOO". * *

* See {@link Formatter} for the format specification and how to customize * it. * *

* This constructor will create a case-insensitive naming strategy. * * @param generatedNameFormat the format to use for generated names */ public ComponentNamingStrategyBase(String generatedNameFormat) { this(generatedNameFormat, true); } /** * Base constructor for a naming strategy with the specified format as the * generated name format. The format will receive two arguments: *

    *
  1. proposed name as {@link String}
  2. *
  3. an {@link Integer} that attempts to make the name unique within a * context
  4. *
* The simplest format specification utilizing both values is * "%s %d", producing "FOO 1". Another example of a useful name * format is "%s%04d", producing "FOO0001". Reordering the * arguments is also possible, e.g. "%2$03d %1$s", producing * "001 FOO". * *

* See {@link Formatter} for the format specification and how to customize * it. * * @param generatedNameFormat the format to use for generated names * @param caseInsensitive controls whether the strategy shall be case-insensitive or not */ public ComponentNamingStrategyBase(String generatedNameFormat, boolean caseInsensitive) { this.generatedNameFormat = generatedNameFormat; this.caseInsensitive = caseInsensitive; } @Override public String findFreshInstanceName(ReadGraph graph, Resource configurationRoot, Resource container, Resource componentType) throws NamingException, DatabaseException { String proposition = ComponentNamingUtil.generateProposition(graph, container, componentType); return validateInstanceName(graph, configurationRoot, container, componentType, proposition); } @Override public String validateInstanceName(ReadGraph graph, Resource configurationRoot, Resource container, Resource componentType, String proposition) throws NamingException, DatabaseException { return validateInstanceName(graph, configurationRoot, container, componentType, proposition, false); } @Override public List validateInstanceNames( ReadGraph graph, Resource configurationRoot, List propositions, boolean acceptProposition, Set externallyReserved) throws NamingException, DatabaseException { throw new UnsupportedOperationException(); } protected Comparator getComparator() { return getComparator(caseInsensitive); } protected static Comparator getComparator(boolean caseInsensitive) { return caseInsensitive ? CASE_INSENSITIVE_STRING_CHARBUFFER_COMPARATOR : CASE_SENSITIVE_STRING_CHARBUFFER_COMPARATOR; } protected static final Comparator CASE_SENSITIVE_STRING_CHARBUFFER_COMPARATOR = new Comparator() { @Override public int compare(Object o1, Object o2) { String s1 = null; String s2 = null; if (o1 instanceof String) s1 = (String) o1; if (o2 instanceof String) s2 = (String) o2; if (s1 != null && s2 != null) return s1.compareTo((String) o2); if (s1 == null && s2 == null) return 0; if (s1 == null && (o1 instanceof CharBuffer)) return -compare(s2, (CharBuffer) o1); if (s2 == null && (o2 instanceof CharBuffer)) return compare(s1, (CharBuffer) o2); return 0; } int compare(String s, CharBuffer buf) { int len1 = s.length(); int len2 = buf.position(); int n = Math.min(len1, len2); int k = 0; while (k < n) { char c1 = s.charAt(k); char c2 = buf.get(k); if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2; } }; protected static final Comparator CASE_INSENSITIVE_STRING_CHARBUFFER_COMPARATOR = new Comparator() { @Override public int compare(Object o1, Object o2) { String s1 = null; String s2 = null; if (o1 instanceof String) s1 = (String) o1; if (o2 instanceof String) s2 = (String) o2; if (s1 != null && s2 != null) return s1.compareToIgnoreCase((String) o2); if (s1 == null && s2 == null) return 0; if (s1 == null && (o1 instanceof CharBuffer)) return -compare(s2, (CharBuffer) o1); if (s2 == null && (o2 instanceof CharBuffer)) return compare(s1, (CharBuffer) o2); return 0; } int compare(String s, CharBuffer buf) { int len1 = s.length(); int len2 = buf.position(); int n = Math.min(len1, len2); int k = 0; while (k < n) { char c1 = s.charAt(k); char c2 = buf.get(k); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { return c1 - c2; } } } k++; } return len1 - len2; } }; protected Set findStartsWithMatches(Set matchUniverse, String proposition) { Set result = new TreeSet(getComparator()); if (matchUniverse.isEmpty()) return result; for (String name : matchUniverse) if (name.startsWith(proposition)) result.add(name); return result; } protected Set findStartsWithMatches(Set matchUniverse, String proposition, Set result) { if (matchUniverse.isEmpty()) return result; for (String name : matchUniverse) if (name.startsWith(proposition)) result.add(name); return result; } protected String findFreshName(Set allReservedNames, Set allRequestedNames, String proposition, boolean acceptProposition) { if (allReservedNames == null) throw new NullPointerException("null reserved name set"); if (proposition == null) throw new NullPointerException("null proposition"); Set startsWithMatches = findStartsWithMatches(allReservedNames, proposition); if (allRequestedNames != null) findStartsWithMatches(allRequestedNames, proposition, startsWithMatches); return findFreshName(startsWithMatches, proposition, acceptProposition); } protected String findFreshName(Set reserved, String proposition, boolean acceptProposition) { return findFreshName(reserved, proposition, acceptProposition, generatedNameFormat); } protected String findFreshName(Set reserved, String proposition, boolean acceptProposition, String nameFormat) { if (proposition == null) throw new NullPointerException("null proposition"); if (DEBUG_NAME_MATCHING) System.out.println("Finding fresh proposed name '" + proposition + "' among matching reservations: " + reserved); if (acceptProposition && !reserved.contains(proposition)) return proposition; // Trying to optimize away unnecessary allocation of new String instances // when looking for fresh names for objects. CharBuffer cb = CharBuffer.allocate(proposition.length() + 10); cb.mark(); @SuppressWarnings("resource") Formatter formatter = new Formatter(cb, Locale.US); //int i = reserved.size() + 1; int i = 1; for (;; ++i) { cb.reset(); formatter.format(nameFormat, proposition, i); if (!reserved.contains(cb)) { // Construct a String from the CharBuffer cb.limit(cb.position()); cb.rewind(); String result = cb.toString(); if (DEBUG_NAME_MATCHING) System.out.println("\tfound fresh name: " + result); return result; } } } }