From db015acf33d725ccdd0b76742b251a0691406087 Mon Sep 17 00:00:00 2001 From: Fredi Pfister Date: Thu, 13 Nov 2025 06:09:50 +0100 Subject: [PATCH] feat: QualifiedNameLookup extensions - allow one object to be initialized from another - make QualifiedNameSegmentTreeLookup externalizable --- .../META-INF/MANIFEST.MF | 7 +- .../QualifiedNameSegmentTreeLookupTest.java | 375 ++++++++++-------- .../ddk/xtext/naming/QualifiedNameLookup.java | 12 + .../QualifiedNameSegmentTreeLookup.java | 96 ++++- .../tools/ddk/xtext/naming/TreeSetLookup.java | 5 + 5 files changed, 315 insertions(+), 180 deletions(-) diff --git a/com.avaloq.tools.ddk.xtext.test/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.xtext.test/META-INF/MANIFEST.MF index 38f21ffd49..865c053be5 100644 --- a/com.avaloq.tools.ddk.xtext.test/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.xtext.test/META-INF/MANIFEST.MF @@ -33,20 +33,21 @@ Require-Bundle: com.avaloq.tools.ddk.xtext, junit-platform-suite-api, org.eclipse.xtext.testing, org.opentest4j -Import-Package: org.slf4j, org.apache.log4j, - com.avaloq.tools.ddk.check.runtime.test.core, +Import-Package: com.avaloq.tools.ddk.check.runtime.test.core, com.avaloq.tools.ddk.check.test.core, com.avaloq.tools.ddk.check.test.runtime.tests, com.avaloq.tools.ddk.check.ui.test, com.avaloq.tools.ddk.checkcfg.test, com.avaloq.tools.ddk.checkcfg.ui.test, + com.avaloq.tools.ddk.sample.helloworld.test, com.avaloq.tools.ddk.typesystem.test, com.avaloq.tools.ddk.xtext.generator.test.generator, com.avaloq.tools.ddk.xtext.generator.util, com.avaloq.tools.ddk.xtext.test.export, com.avaloq.tools.ddk.xtext.test.format, com.avaloq.tools.ddk.xtext.ui.test, - com.avaloq.tools.ddk.sample.helloworld.test + org.apache.log4j, + org.slf4j Export-Package: com.avaloq.tools.ddk.xtext, com.avaloq.tools.ddk.xtext.formatter, com.avaloq.tools.ddk.xtext.formatter.services, diff --git a/com.avaloq.tools.ddk.xtext.test/src/com/avaloq/tools/ddk/xtext/naming/QualifiedNameSegmentTreeLookupTest.java b/com.avaloq.tools.ddk.xtext.test/src/com/avaloq/tools/ddk/xtext/naming/QualifiedNameSegmentTreeLookupTest.java index 55ae55dd2e..ffe58a7918 100644 --- a/com.avaloq.tools.ddk.xtext.test/src/com/avaloq/tools/ddk/xtext/naming/QualifiedNameSegmentTreeLookupTest.java +++ b/com.avaloq.tools.ddk.xtext.test/src/com/avaloq/tools/ddk/xtext/naming/QualifiedNameSegmentTreeLookupTest.java @@ -1,173 +1,202 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * 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: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.xtext.naming; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -import java.util.Collection; -import java.util.Collections; - -import org.eclipse.emf.common.util.URI; -import org.eclipse.xtext.naming.QualifiedName; -import org.junit.jupiter.api.Test; - -import com.google.common.collect.ImmutableSet; - - -@SuppressWarnings({"nls", "unused", "PMD.JUnitAssertionsShouldIncludeMessage"}) -// CHECKSTYLE:CHECK-OFF MultipleStringLiteralsCheck -public class QualifiedNameSegmentTreeLookupTest { - - private final QualifiedNameSegmentTreeLookup lookup = new QualifiedNameSegmentTreeLookup(URI.class, true); - - @Test - public void testEmpty() { - assertNull(lookup.get(QualifiedName.EMPTY)); - } - - @Test - public void testExact() { - QualifiedName name = name("foo"); - Collection values = Collections.singletonList(uri(name)); - lookup.putAll(name, values); - assertContentEquals(values, lookup.get(name)); - - name = name("foo.bar"); - values = Collections.singletonList(uri(name)); - lookup.putAll(name, values); - assertContentEquals(values, lookup.get(name)); - - name = name("bar"); - values = Collections.singletonList(uri(name)); - lookup.putAll(name, values); - assertContentEquals(values, lookup.get(name)); - } - - @Test - public void testTopLevelPatternWithoutWildcard() { - URI value1 = put("foo"); - URI value2 = put("bar"); - URI value3 = put("foo2"); - - assertContentEquals(ImmutableSet.of(value1), lookup.get(pattern("foo"), false)); - assertContentEquals(ImmutableSet.of(value2), lookup.get(pattern("bar"), false)); - assertContentEquals(ImmutableSet.of(value3), lookup.get(pattern("foo2"), false)); - } - - @Test - public void testTopLevelPatternWithWildcard() { - URI value1 = put("foo"); - URI value2 = put("foo2"); - URI value3 = put("bar"); - - assertContentEquals(ImmutableSet.of(value1, value2), lookup.get(pattern("f*"), true)); - assertContentEquals(ImmutableSet.of(value1, value2), lookup.get(pattern("foo*"), true)); - assertContentEquals(ImmutableSet.of(value3), lookup.get(pattern("b*"), true)); - } - - @Test - public void testNestedPatternMatchesWithoutWildcard() { - URI value1 = put("foo"); - URI value2 = put("foo.bar"); - URI value3 = put("foo2"); - - assertContentEquals(ImmutableSet.of(value1), lookup.get(pattern("foo"), true)); - assertContentEquals(ImmutableSet.of(value2), lookup.get(pattern("foo.bar"), true)); - assertContentEquals(ImmutableSet.of(value3), lookup.get(pattern("foo2"), true)); - } - - @Test - public void testNestedPatternMatchesWithWildcard() { - URI value1 = put("foo"); - URI value2 = put("foo.bar"); - URI value3 = put("foo.baz"); - URI value4 = put("foo.baz.bazz"); - URI value5 = put("foo2"); - - assertContentEquals(ImmutableSet.of(value1, value5), lookup.get(pattern("f*"), true)); - assertContentEquals(ImmutableSet.of(value2, value3), lookup.get(pattern("foo.*"), true)); - assertContentEquals(ImmutableSet.of(value2, value3), lookup.get(pattern("foo.ba*"), true)); - assertContentEquals(ImmutableSet.of(value2), lookup.get(pattern("foo.bar*"), true)); - } - - @Test - public void testNestedPatternMatchesWithRecursiveWildcard() { - URI value1 = put("foo"); - URI value2 = put("foo.bar"); - URI value3 = put("foo.bar.baz"); - URI value4 = put("foo.bar.baz.quux"); - URI value5 = put("foo.foo"); - URI value6 = put("foo2"); - - assertContentEquals(ImmutableSet.of(value2, value3, value4, value5), lookup.get(pattern("foo.**"), true)); - assertContentEquals(ImmutableSet.of(value2, value3, value4), lookup.get(pattern("foo.b**"), true)); - } - - @Test - public void testUnmatchedNestedPattern() { - URI value1 = put("foo"); - URI value2 = put("foo.bar"); - URI value3 = put("foo.bar.baz"); - URI value4 = put("foo.bar.baz.quux"); - URI value5 = put("foo.foo"); - URI value6 = put("foo2"); - - assertContentEquals(ImmutableSet.of(), lookup.get(pattern("e*"), true)); - assertContentEquals(ImmutableSet.of(), lookup.get(pattern("g*"), true)); - assertContentEquals(ImmutableSet.of(), lookup.get(pattern("foa.*"), true)); - assertContentEquals(ImmutableSet.of(), lookup.get(pattern("fon.b*"), true)); - assertContentEquals(ImmutableSet.of(), lookup.get(pattern("foo.c*"), true)); - assertContentEquals(ImmutableSet.of(), lookup.get(pattern("foo.baq.b*"), true)); - assertContentEquals(ImmutableSet.of(), lookup.get(pattern("foo.bar.a*"), true)); - assertContentEquals(ImmutableSet.of(), lookup.get(pattern("foo.bar.bazz*"), true)); - } - - @Test - public void testOutOfOrderInsertion() { - QualifiedName name1 = name("foo.bar"); - Collection value1 = Collections.singletonList(uri(name1)); - lookup.putAll(name1, value1); - assertContentEquals(value1, lookup.get(name1)); - - QualifiedName name2 = name("foo"); - Collection value2 = Collections.singletonList(uri(name2)); - lookup.putAll(name2, value2); - assertContentEquals(value2, lookup.get(name2)); - } - - private QualifiedName name(final String str) { - return QualifiedNames.safeQualifiedName(str); - } - - private QualifiedNamePattern pattern(final String str) { - return pattern(name(str)); - } - - private QualifiedNamePattern pattern(final QualifiedName name) { - return QualifiedNamePattern.create(name); - } - - public URI uri(final QualifiedName name) { - return URI.createURI("scheme:/" + name); - } - - private URI put(final String name) { - QualifiedName qname = name(name); - URI value = uri(qname); - lookup.put(qname, value); - return value; - } - - public void assertContentEquals(final Collection expected, final Collection actual) { - assertEquals(ImmutableSet.copyOf(expected), ImmutableSet.copyOf(actual)); - } -} +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * 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: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.naming; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.xtext.naming.QualifiedName; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableSet; + + +@SuppressWarnings({"nls", "unused", "PMD.JUnitAssertionsShouldIncludeMessage"}) +// CHECKSTYLE:CHECK-OFF MultipleStringLiteralsCheck +public class QualifiedNameSegmentTreeLookupTest { + private final QualifiedNameSegmentTreeLookup lookup = new QualifiedNameSegmentTreeLookup(URI.class, true); + + @Test + public void testEmpty() { + assertNull(lookup.get(QualifiedName.EMPTY)); + } + + @Test + public void testExact() { + QualifiedName name = name("foo"); + Collection values = Collections.singletonList(uri(name)); + lookup.putAll(name, values); + assertContentEquals(values, lookup.get(name)); + + name = name("foo.bar"); + values = Collections.singletonList(uri(name)); + lookup.putAll(name, values); + assertContentEquals(values, lookup.get(name)); + + name = name("bar"); + values = Collections.singletonList(uri(name)); + lookup.putAll(name, values); + assertContentEquals(values, lookup.get(name)); + } + + @Test + public void testTopLevelPatternWithoutWildcard() { + URI value1 = put("foo"); + URI value2 = put("bar"); + URI value3 = put("foo2"); + + assertContentEquals(ImmutableSet.of(value1), lookup.get(pattern("foo"), false)); + assertContentEquals(ImmutableSet.of(value2), lookup.get(pattern("bar"), false)); + assertContentEquals(ImmutableSet.of(value3), lookup.get(pattern("foo2"), false)); + } + + @Test + public void testTopLevelPatternWithWildcard() { + URI value1 = put("foo"); + URI value2 = put("foo2"); + URI value3 = put("bar"); + + assertContentEquals(ImmutableSet.of(value1, value2), lookup.get(pattern("f*"), true)); + assertContentEquals(ImmutableSet.of(value1, value2), lookup.get(pattern("foo*"), true)); + assertContentEquals(ImmutableSet.of(value3), lookup.get(pattern("b*"), true)); + } + + @Test + public void testNestedPatternMatchesWithoutWildcard() { + URI value1 = put("foo"); + URI value2 = put("foo.bar"); + URI value3 = put("foo2"); + + assertContentEquals(ImmutableSet.of(value1), lookup.get(pattern("foo"), true)); + assertContentEquals(ImmutableSet.of(value2), lookup.get(pattern("foo.bar"), true)); + assertContentEquals(ImmutableSet.of(value3), lookup.get(pattern("foo2"), true)); + } + + @Test + public void testNestedPatternMatchesWithWildcard() { + URI value1 = put("foo"); + URI value2 = put("foo.bar"); + URI value3 = put("foo.baz"); + URI value4 = put("foo.baz.bazz"); + URI value5 = put("foo2"); + + assertContentEquals(ImmutableSet.of(value1, value5), lookup.get(pattern("f*"), true)); + assertContentEquals(ImmutableSet.of(value2, value3), lookup.get(pattern("foo.*"), true)); + assertContentEquals(ImmutableSet.of(value2, value3), lookup.get(pattern("foo.ba*"), true)); + assertContentEquals(ImmutableSet.of(value2), lookup.get(pattern("foo.bar*"), true)); + } + + @Test + public void testNestedPatternMatchesWithRecursiveWildcard() { + URI value1 = put("foo"); + URI value2 = put("foo.bar"); + URI value3 = put("foo.bar.baz"); + URI value4 = put("foo.bar.baz.quux"); + URI value5 = put("foo.foo"); + URI value6 = put("foo2"); + + assertContentEquals(ImmutableSet.of(value2, value3, value4, value5), lookup.get(pattern("foo.**"), true)); + assertContentEquals(ImmutableSet.of(value2, value3, value4), lookup.get(pattern("foo.b**"), true)); + } + + @Test + public void testUnmatchedNestedPattern() { + URI value1 = put("foo"); + URI value2 = put("foo.bar"); + URI value3 = put("foo.bar.baz"); + URI value4 = put("foo.bar.baz.quux"); + URI value5 = put("foo.foo"); + URI value6 = put("foo2"); + + assertContentEquals(ImmutableSet.of(), lookup.get(pattern("e*"), true)); + assertContentEquals(ImmutableSet.of(), lookup.get(pattern("g*"), true)); + assertContentEquals(ImmutableSet.of(), lookup.get(pattern("foa.*"), true)); + assertContentEquals(ImmutableSet.of(), lookup.get(pattern("fon.b*"), true)); + assertContentEquals(ImmutableSet.of(), lookup.get(pattern("foo.c*"), true)); + assertContentEquals(ImmutableSet.of(), lookup.get(pattern("foo.baq.b*"), true)); + assertContentEquals(ImmutableSet.of(), lookup.get(pattern("foo.bar.a*"), true)); + assertContentEquals(ImmutableSet.of(), lookup.get(pattern("foo.bar.bazz*"), true)); + } + + @Test + public void testOutOfOrderInsertion() { + QualifiedName name1 = name("foo.bar"); + Collection value1 = Collections.singletonList(uri(name1)); + lookup.putAll(name1, value1); + assertContentEquals(value1, lookup.get(name1)); + + QualifiedName name2 = name("foo"); + Collection value2 = Collections.singletonList(uri(name2)); + lookup.putAll(name2, value2); + assertContentEquals(value2, lookup.get(name2)); + } + + @Test + public void testLoadStore() throws IOException, ClassNotFoundException { + List nameList = List.of(name("foo"), name("foo.bar"), name("bar")); + for (QualifiedName qn : nameList) { + lookup.put(qn, uri(qn)); + } + ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutStream = new ObjectOutputStream(byteOutStream); + objectOutStream.writeObject(lookup); + byte[] data = byteOutStream.toByteArray(); + + ObjectInputStream inStream = new ObjectInputStream(new ByteArrayInputStream(data)); + Object readBack = inStream.readObject(); + + assertEquals(readBack.getClass(), lookup.getClass()); + + @SuppressWarnings("unchecked") + QualifiedNameLookup readBackLookup = (QualifiedNameLookup) readBack; + + for (QualifiedName qn : nameList) { + assertEquals(lookup.get(qn), readBackLookup.get(qn)); + } + } + + private QualifiedName name(final String str) { + return QualifiedNames.safeQualifiedName(str); + } + + private QualifiedNamePattern pattern(final String str) { + return pattern(name(str)); + } + + private QualifiedNamePattern pattern(final QualifiedName name) { + return QualifiedNamePattern.create(name); + } + + public URI uri(final QualifiedName name) { + return URI.createURI("scheme:/" + name); + } + + private URI put(final String name) { + QualifiedName qname = name(name); + URI value = uri(qname); + lookup.put(qname, value); + return value; + } + + public void assertContentEquals(final Collection expected, final Collection actual) { + assertEquals(ImmutableSet.copyOf(expected), ImmutableSet.copyOf(actual)); + } +} diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/naming/QualifiedNameLookup.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/naming/QualifiedNameLookup.java index d71ebf3285..2303b959e9 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/naming/QualifiedNameLookup.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/naming/QualifiedNameLookup.java @@ -93,4 +93,16 @@ public interface QualifiedNameLookup extends ICache { */ void clear(); + /** + * Initializes this QualifiedNameLookup from source. + * Performs a shallow copy, i.e. values are shared between the objects after the operation. + * Only allowed if this object is empty. + * + * @param source + * QualifiedNameLookup to initialize from + * @throws IllegalStateException + * if this object is not empty. + */ + void initializeFrom(QualifiedNameLookup source); + } diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/naming/QualifiedNameSegmentTreeLookup.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/naming/QualifiedNameSegmentTreeLookup.java index 44a6229f29..f8fb2122c6 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/naming/QualifiedNameSegmentTreeLookup.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/naming/QualifiedNameSegmentTreeLookup.java @@ -10,6 +10,10 @@ *******************************************************************************/ package com.avaloq.tools.ddk.xtext.naming; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; @@ -17,6 +21,7 @@ import java.util.List; import java.util.Set; +import org.eclipse.emf.common.util.URI; import org.eclipse.xtext.naming.QualifiedName; import com.avaloq.tools.ddk.annotations.SuppressFBWarnings; @@ -43,7 +48,8 @@ * @param * value element type */ -public class QualifiedNameSegmentTreeLookup implements QualifiedNameLookup { +public class QualifiedNameSegmentTreeLookup implements QualifiedNameLookup, Externalizable { + private static final long serialVersionUID = 1L; /** * A node in the tree representing a single segment of a qualified name. @@ -51,14 +57,19 @@ public class QualifiedNameSegmentTreeLookup implements QualifiedNameLookup // CHECKSTYLE:CHECK-OFF ParameterAssignmentCheck // CHECKSTYLE:CHECK-OFF FinalParametersCheck // CHECKSTYLE:CHECK-OFF VisibilityModifier - private static class SegmentNode { + private static class SegmentNode implements Externalizable { + private static final long serialVersionUID = 1L; protected static final int DEFAULT_CHILD_CAPACITY = 4; - private final String segment; + private String segment; protected Object[] values; protected List children; + public SegmentNode() { + // required by externalization + } + /** * Creates a new node for the given qualified name segment. * @@ -306,12 +317,58 @@ protected int binarySearch(final List list, final String key) { public String toString() { return "Node(" + segment + ")"; //$NON-NLS-1$ //$NON-NLS-2$ } + + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeObject(segment); + if (values == null) { + out.writeInt(0); + } else { + out.writeInt(values.length); + for (Object value : values) { + if (value instanceof URI uri) { + out.writeBoolean(true); + out.writeObject(uri.toString()); + } else { + out.writeBoolean(false); + out.writeObject(value); + } + } + } + out.writeObject(children); + } + + @SuppressWarnings("unchecked") + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + segment = (String) in.readObject(); + int valueLength = in.readInt(); + if (valueLength > 0) { + values = new Object[valueLength]; + for (int i = 0; i < valueLength; i++) { + Boolean convertToURI = in.readBoolean(); + Object readBack = in.readObject(); + if (convertToURI) { // do not test with instanceof: if convertToURI is true but the object read is not a String + // then we want the CCE because something went wrong + values[i] = URI.createURI((String) readBack); + } else { + values[i] = readBack; + } + } + } + children = (List) in.readObject(); + } } /** * Subclass which implements value sharing between nodes and child nodes to reduce the memory footprint. */ private static class ValueSharingSegmentNode extends SegmentNode { + private static final long serialVersionUID = 1L; + + public ValueSharingSegmentNode() { + // required by externalization + } ValueSharingSegmentNode(final String segment) { super(segment); @@ -384,7 +441,7 @@ private abstract static class Visitor { public abstract void visit(SegmentNode node); } - private final SegmentNode root; + private SegmentNode root; private long size; private long hits; @@ -417,6 +474,10 @@ public static QualifiedNameLookup createNameLookupCache(final String n return cache; } + public QualifiedNameSegmentTreeLookup() { + // required by externalisation + } + public QualifiedNameSegmentTreeLookup(final Class elementType, final boolean shareValues) { // NOPMD root = shareValues ? new ValueSharingSegmentNode("") : new SegmentNode(""); //$NON-NLS-1$ //$NON-NLS-2$ init(); @@ -518,4 +579,31 @@ public CacheStatistics getStatistics() { return new CacheStatistics(size, hits, misses); } + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeLong(size); + out.writeObject(root); + } + + @Override + @SuppressFBWarnings("AT_NONATOMIC_64BIT_PRIMITIVE") + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + size = in.readLong(); + root = (SegmentNode) in.readObject(); + } + + @Override + @SuppressFBWarnings("AT_NONATOMIC_64BIT_PRIMITIVE") + public void initializeFrom(final QualifiedNameLookup source) { + if (size > 0) { + throw new IllegalStateException("must not initialize non-empty object"); //$NON-NLS-1$ + } + if (!(source instanceof QualifiedNameSegmentTreeLookup other)) { + throw new UnsupportedOperationException("cannot copy from " + source.getClass()); //$NON-NLS-1$ + } else { + this.size = other.size; + this.root = other.root; + } + } + } diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/naming/TreeSetLookup.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/naming/TreeSetLookup.java index 41259476c5..45dbc79d30 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/naming/TreeSetLookup.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/naming/TreeSetLookup.java @@ -114,4 +114,9 @@ public CacheStatistics getStatistics() { return new CacheStatistics(lookupMap.size(), hits, misses); } + @Override + public void initializeFrom(final QualifiedNameLookup source) { + throw new UnsupportedOperationException(); + } + }