package rocks.inspectit.server.instrumentation.classcache.index; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import rocks.inspectit.server.instrumentation.classcache.events.INodeChangeListener; import rocks.inspectit.server.instrumentation.classcache.events.NodeEvent; import rocks.inspectit.server.instrumentation.classcache.events.NodeEvent.NodeEventType; import rocks.inspectit.server.instrumentation.classcache.events.ReferenceEvent; import rocks.inspectit.shared.all.instrumentation.classcache.Type; import rocks.inspectit.shared.all.instrumentation.classcache.util.TypeSet; import rocks.inspectit.shared.all.pattern.IMatchPattern; import rocks.inspectit.shared.all.pattern.WildcardMatchPattern; /** * Fast type indexer by FQN name. Indexer can locate types by exact name or by startsWith approach. * <p> * Note that this indexer should not be used with multiple threads reading and writing. Multiple * threads reading is OK. * * @author Ivan Senic * * @param <E> * Type being indexed. */ @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Lazy public class FqnIndexer<E extends Type> extends TypeSet<E> implements INodeChangeListener { /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public void informNodeChange(NodeEvent event) { if (NodeEventType.NEW.equals(event.getEventType())) { index((E) event.getType()); } else if (NodeEventType.REMOVED.equals(event.getEventType())) { remove(event.getType()); } } /** * {@inheritDoc} */ @Override public void informReferenceChange(ReferenceEvent event) { // not interesting } /** * Index one type. * * @param type * Type to index. */ void index(E type) { addOrUpdate(type); } /** * Finds type by exact FQN name. * * @param fqn * Fully qualified class name. * @return Returns type or <code>null</code> if it can not be found. */ public E lookup(String fqn) { int index = findRetrieveIndexFor(fqn); return getAt(index); } /** * Finds all {@link NonPrimitiveType}s that start with given string. * * @param fqnWildCard * String that class should start with. * @return All types starting with the given string. */ public Collection<E> findStartsWith(String fqnWildCard) { long minMaxIndex = findStartsWithMinMaxIndexes(fqnWildCard); int min = getLowerInt(minMaxIndex); int max = getUpperInt(minMaxIndex); if (min < 0) { return Collections.emptyList(); } int size = size(); List<E> results = new ArrayList<>((max - min) + 1); for (int i = min; (i <= max) && (max < size); i++) { results.add(getAt(i)); } return results; } /** * Finds all indexed types. * * @return All currently indexed types. */ public Collection<E> findAll() { return new ArrayList<>(this); } /** * Finds types by {@link IMatchPattern}. FQN of each returned type will match the given pattern. * * @param matchPattern * {@link IMatchPattern} * @return Collection of types which FQN matches the pattern. */ public Collection<E> findByPattern(IMatchPattern matchPattern) { String template = matchPattern.getPattern(); if (null == template) { return Collections.emptyList(); } Collection<E> results; if (WildcardMatchPattern.isPattern(template)) { // for wild card use startsWith method String startsWithCriteria = template.substring(0, template.indexOf('*')); results = findStartsWith(startsWithCriteria); // make sure each type it's fitting to the pattern for (Iterator<E> it = results.iterator(); it.hasNext();) { E element = it.next(); if (!matchPattern.match(element.getFQN())) { it.remove(); } } } else { E type = lookup(template); if (null != type) { results = new ArrayList<>(1); results.add(type); } else { results = Collections.emptyList(); } } return results; } /** * Finds index for a FQN to retrieve. * * @param fqn * String representing the FQN. * @return Index where element should be retrieved from or negative value if one can not be * located. */ private int findRetrieveIndexFor(String fqn) { int size = size(); int min = 0; int max = size - 1; while (max >= min) { int mid = midpoint(min, max); int compare = getAt(mid).getFQN().compareTo(fqn); // if no difference then we have it if (0 == compare) { return mid; } // otherwise adapt min and max min = (compare < 0) ? mid + 1 : min; max = (compare > 0) ? mid - 1 : max; } return -1; } /** * Find elements that starts with given {@link String}. * * @param fqnWildCard * String that elements should start with. * @return Min and max index packed in a long. Min index is in lower int, while max index is in * upper int. If no element is found, min index will be packed as -1. */ private long findStartsWithMinMaxIndexes(String fqnWildCard) { int size = size(); int minIndex = -1; int maxIndex = -1; // first search for any element that starts with and remember the index int anyElementIndex = -1; int min = 0; int max = size - 1; while (max >= min) { int mid = midpoint(min, max); String fqn = getAt(mid).getFQN(); if (fqn.startsWith(fqnWildCard)) { // setting index for the found element in both anyElementIndex = mid; break; } int compare = fqn.compareTo(fqnWildCard); // otherwise adapt min and max min = (compare < 0) ? mid + 1 : min; max = (compare > 0) ? mid - 1 : max; } // if we did not find any element then return nothing if (anyElementIndex == -1) { return pack(0, -1); } // else first go for minimum index then // somewhere between 0 and anyElementIndex min = 0; max = anyElementIndex; while (max >= min) { int mid = midpoint(min, max); int compare = getAt(mid).getFQN().startsWith(fqnWildCard) ? 1 : -1; // adapt min and max min = (compare < 0) ? mid + 1 : min; max = (compare > 0) ? mid - 1 : max; if (compare > 0) { minIndex = mid; } if (mid == min) { break; } } // and then for maximum index // somewhere between anyElementIndex and last min = anyElementIndex; max = size - 1; while (max >= min) { int mid = midpoint(min, max); int compare = getAt(mid).getFQN().startsWith(fqnWildCard) ? -1 : 1; // adapt min and max min = (compare < 0) ? mid + 1 : min; max = (compare > 0) ? mid - 1 : max; if (compare < 0) { maxIndex = mid; } if (mid == max) { break; } } // pack result and return return pack(maxIndex, minIndex); } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder sb = new StringBuilder(); int size = size(); for (int i = 0; i < size; i++) { sb.append("[" + i + "] = " + getAt(i).getFQN() + "\n"); } return sb.toString(); } }