/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.modeling.services; import java.util.ArrayList; import java.util.Comparator; import java.util.Formatter; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.simantics.databoard.Bindings; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.Indexing; import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener; import org.simantics.db.common.request.PossibleIndexRoot; import org.simantics.db.common.request.UnaryRead; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.genericrelation.Dependencies; import org.simantics.db.layer0.genericrelation.IndexQueries; import org.simantics.db.service.GraphChangeListenerSupport; import org.simantics.layer0.Layer0; import org.simantics.scl.runtime.function.Function; import org.simantics.scl.runtime.function.Function1; import org.simantics.scl.runtime.tuple.Tuple4; import org.simantics.structural.stubs.StructuralResource2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import gnu.trove.set.hash.THashSet; /** * A component naming strategy implementation for structural models based on an * SCL function that lists used names in a model. * *

* The type of the function is expected to be: * ReadGraph => Resource -> String -> Integer -> List<Resource> * * @author Tuukka Lehtonen * * @see ComponentNamingStrategy */ public class CaseInsensitiveComponentFunctionNamingStrategy extends ComponentNamingStrategyBase { private static final Logger LOGGER = LoggerFactory.getLogger(CaseInsensitiveComponentFunctionNamingStrategy.class); protected static final boolean DEBUG_INDEX_SEARCH = false | DEBUG_ALL; @SuppressWarnings("rawtypes") private final Function index; /** * A filter that is applied to all user-provided name propositions before * processing them. */ private Function1 propositionPreFilter; /** * Construct an index-based naming strategy with "%s %d" as the generated * name format. See {@link Formatter} for the format specification. * * @param index the index function for looking up used names. The function * must be of type * ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>> */ @SuppressWarnings("rawtypes") public CaseInsensitiveComponentFunctionNamingStrategy(Function index) { this("%s %d", index); } /** * Construct an index-based naming strategy with the specified format as the * generated name format. See {@link Formatter} for the format * specification. * * @param generatedNameFormat the format to use for generated names, see * {@link Formatter} * @param index the index function for looking up used names. The function * must be of type * ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>> */ @SuppressWarnings("rawtypes") public CaseInsensitiveComponentFunctionNamingStrategy(String generatedNameFormat, Function index) { super(generatedNameFormat); this.index = index; } /** * Construct an index-based naming strategy with the specified format as the * generated name format. See {@link Formatter} for the format * specification. * * @param generatedNameFormat the format to use for generated names, see * {@link Formatter} * @param index the index function for looking up used names. The function * must be of type * ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>> * @param an optional function to */ @SuppressWarnings("rawtypes") public CaseInsensitiveComponentFunctionNamingStrategy(String generatedNameFormat, Function index, Function1 propositionPreFilter) { super(generatedNameFormat); this.index = index; this.propositionPreFilter = propositionPreFilter; } CaseInsensitiveComponentNamingStrategy2 fallbackStrategy = null; @Override public String validateInstanceName(ReadGraph graph, Resource configurationRoot, Resource component, String proposition, boolean acceptProposition) throws NamingException, DatabaseException { String lowercaseProposition = proposition.toLowerCase(); Layer0 L0 = Layer0.getInstance(graph); String name = graph.getPossibleRelatedValue(component, L0.HasName, Bindings.STRING); if (name != null) { String lowercaseName = name.toLowerCase(); if(lowercaseName.startsWith(lowercaseProposition)) { Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot)); if (indexRoot != null) { synchronized (this) { String search = IndexQueries.escapeTerm(Dependencies.FIELD_NAME_SEARCH, lowercaseName, true) + "*"; //$NON-NLS-1$ @SuppressWarnings("unchecked") List components = (List) index.apply(graph, indexRoot, search, Integer.MAX_VALUE); Set rs = new THashSet(); for (Resource componentResult : components) { if (DEBUG_INDEX_SEARCH) LOGGER.info(getClass().getSimpleName() + ": found " + componentResult); String n = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING); if (n != null && n.toLowerCase().equals(lowercaseName)) rs.add(componentResult); } Cache c = getCache(graph, indexRoot); if(rs.size() == 0) { if(!c.getRequested().contains(name)) { c.addRequested(name); return name; } } else if(rs.size() == 1) { if(component.equals(rs.iterator().next())) { return name; } } } } } } StructuralResource2 STR = StructuralResource2.getInstance(graph); Resource container = graph.getSingleObject(component, L0.PartOf); Resource componentType = graph.getSingleType(component, STR.Component); return validateInstanceName(graph, configurationRoot, container, componentType, proposition, acceptProposition); } static class ComponentsRequest extends UnaryRead>{ public ComponentsRequest(Tuple4 parameter) { super(parameter); } @Override public Set perform(ReadGraph graph) throws DatabaseException { Resource indexRoot = (Resource)parameter.get(0); Function index = (Function)parameter.get(1); String search = (String)parameter.get(2); Comparator comparator = (Comparator)parameter.get(3); List components = (List) index.apply(graph, indexRoot, search, Integer.MAX_VALUE); Set reserved = new TreeSet(comparator); Layer0 L0 = Layer0.getInstance(graph); for (Resource componentResult : components) { if (DEBUG_INDEX_SEARCH) LOGGER.info(getClass().getSimpleName() + ": found " + componentResult); String name = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING); if (name != null) reserved.add(name); } LOGGER.warn("found " + reserved.size() + " components"); return reserved; } } @Override public String validateInstanceName(ReadGraph graph, Resource configurationRoot, Resource container, Resource componentType, String proposition, boolean acceptProposition) throws NamingException, DatabaseException { Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot)); if (indexRoot == null) { LOGGER.warn("Could not find index root from configuration root '" + NameUtils.getSafeName(graph, configurationRoot, true) + "'"); if(fallbackStrategy == null) fallbackStrategy = new CaseInsensitiveComponentNamingStrategy2(graph.getService(GraphChangeListenerSupport.class), generatedNameFormat); return fallbackStrategy.validateInstanceName(graph, configurationRoot, container, componentType, proposition, acceptProposition); } if (propositionPreFilter != null) proposition = propositionPreFilter.apply(proposition); synchronized (this) { String search = Dependencies.FIELD_NAME_SEARCH + ":" + IndexQueries.escape(proposition.toLowerCase(), true) + "*"; //$NON-NLS-1$ //$NON-NLS-2$ Set reserved = graph.syncRequest(new ComponentsRequest(new Tuple4(indexRoot, index, search, getComparator())), TransientCacheAsyncListener.instance()); Cache cache = getCache(graph, indexRoot); findStartsWithMatches(cache.getRequested(), proposition, reserved); String result = findFreshName(reserved, proposition, acceptProposition); cache.addRequested(result); if (DEBUG_INDEX_SEARCH) LOGGER.info(getClass().getSimpleName() + ": validated instance name " + result); return result; } } @Override public List validateInstanceNames( ReadGraph graph, Resource configurationRoot, List propositions, boolean acceptProposition, Set externallyReserved) throws NamingException, DatabaseException { Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot)); if (indexRoot == null) throw new NamingException("Could not find index root from configuration root '" + NameUtils.getSafeName(graph, configurationRoot, true) + "'"); Layer0 L0 = Layer0.getInstance(graph); synchronized (this) { List result = new ArrayList(propositions.size()); Set reserved = new TreeSet(getComparator()); for (String proposition : propositions) { if (propositionPreFilter != null) proposition = propositionPreFilter.apply(proposition); String search = IndexQueries.quoteTerm(Dependencies.FIELD_NAME_SEARCH, proposition.toLowerCase()); @SuppressWarnings("unchecked") List components = (List) index.apply(graph, indexRoot, search, Integer.MAX_VALUE); if (DEBUG_INDEX_SEARCH) LOGGER.info(getClass().getSimpleName() + ": found " + components.size() + " index results for index root " + indexRoot + " & configurationRoot " + configurationRoot + " & proposition '" + proposition + "':"); reserved.clear(); for (Resource componentResult : components) { if (DEBUG_INDEX_SEARCH) LOGGER.info(getClass().getSimpleName() + ": found " + componentResult); String name = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING); if (name != null) reserved.add(name); } if (externallyReserved != null) reserved.addAll(externallyReserved); reserved.addAll(result); String name = findFreshName(reserved, proposition, acceptProposition); result.add(name); if (DEBUG_INDEX_SEARCH) LOGGER.info(getClass().getSimpleName() + ": validated instance name " + proposition + " -> " + name); } if (DEBUG_INDEX_SEARCH) LOGGER.info(getClass().getSimpleName() + ": validated instance names " + propositions + " -> " + result); return result; } } private Cache getCache(ReadGraph graph, Resource root) throws DatabaseException { Cache cache = Indexing.getCache(root, Cache.class); if(cache != null) return cache; synchronized (this) { cache = Indexing.getCache(root, Cache.class); if(cache != null) return cache; return Indexing.createCache(root, new Cache(caseInsensitive)); } } static class Cache { private final Set requested; Cache(boolean caseInsensitive) { this.requested = new TreeSet(getComparator(caseInsensitive)); } public Set getRequested() { return requested; } public void addRequested(String name) { requested.add(name); } @Override public String toString() { return getClass().getSimpleName() + ": " + requested; } } }