// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.mappaint;
import static org.junit.Assert.assertEquals;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.util.IdentityHashMap;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.visitor.paint.Rendering;
import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
import org.openstreetmap.josm.gui.MapView;
import org.openstreetmap.josm.gui.NavigatableComponent;
import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
import org.openstreetmap.josm.io.Compression;
import org.openstreetmap.josm.io.OsmReader;
import org.openstreetmap.josm.testutils.JOSMTestRules;
import org.openstreetmap.josm.tools.Pair;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Test {@link StyleCache}.
*/
public class StyleCacheTest {
private static final int IMG_WIDTH = 1400;
private static final int IMG_HEIGHT = 1050;
private static Graphics2D g;
private static BufferedImage img;
private static NavigatableComponent nc;
private static DataSet dsCity;
private static DataSet dsCity2;
/**
* The test rules used for this test.
*/
@Rule
@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
public JOSMTestRules test = new JOSMTestRules().preferences().platform().projection().mapStyles().timeout(60000);
/**
* Load the test data that is required.
* @throws Exception If an error occurred during load.
*/
@BeforeClass
public static void load() throws Exception {
img = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_ARGB);
try (
InputStream fisC = Compression.getUncompressedFileInputStream(new File("data_nodist/neubrandenburg.osm.bz2"));
) {
dsCity = OsmReader.parseDataSet(fisC, NullProgressMonitor.INSTANCE);
}
dsCity2 = new DataSet(dsCity);
}
/**
* Free the memory allocated for this test.
* <p>
* Since we are running junit in non-forked mode, we don't know when this test will not be referenced any more.
*/
@AfterClass
public static void unload() {
g = null;
img = null;
nc = null;
dsCity = null;
dsCity2 = null;
}
/**
* Create the temporary graphics
*/
@Before
public void loadGraphicComponents() {
g = (Graphics2D) img.getGraphics();
g.setClip(0, 0, IMG_WIDTH, IMG_WIDTH);
g.setColor(Color.BLACK);
g.fillRect(0, 0, IMG_WIDTH, IMG_WIDTH);
nc = new MapView(Main.getLayerManager(), null);
nc.setBounds(0, 0, IMG_WIDTH, IMG_HEIGHT);
}
/**
* Verifies, that the intern pool is not growing when repeatedly rendering the
* same set of primitives (and clearing the calculated styles each time).
*
* If it grows, this is an indication that the {@code equals} and {@code hashCode}
* implementation is broken and two identical objects are not recognized as equal
* or produce different hash codes.
*
* The opposite problem (different objects are mistaken as equal) has more visible
* consequences for the user (wrong rendering on the map) and is not recognized by
* this test.
*/
@Test
public void testStyleCacheInternPool() {
MapPaintStyles.getStyles().clearCached();
StyleCache.clearStyleCachePool();
Bounds bounds = new Bounds(53.56, 13.25, 53.57, 13.26);
Rendering visitor = new StyledMapRenderer(g, nc, false);
nc.zoomTo(bounds);
Integer internPoolSize = null;
for (int i = 0; i < 10; i++) {
visitor.render(dsCity, true, bounds);
MapPaintStyles.getStyles().clearCached();
int newInternPoolSize = StyleCache.getInternPoolSize();
if (internPoolSize == null) {
internPoolSize = newInternPoolSize;
} else {
if (internPoolSize != newInternPoolSize) {
System.err.println("style sources:");
for (StyleSource s : MapPaintStyles.getStyles().getStyleSources()) {
System.err.println(s.url + " active:" + s.active);
}
}
assertEquals("intern pool size", internPoolSize.intValue(), newInternPoolSize);
}
}
}
/**
* Verifies, that the number of {@code StyleElementList} instances stored
* for all the rendered primitives is actually low (as intended).
*
* Two primitives with the same style should share one {@code StyleElementList}
* instance for the cached style elements. This is verified by counting all
* the instances using {@code A == B} identity.
*/
@Test
public void testStyleCacheInternPool2() {
StyleCache.clearStyleCachePool();
Bounds bounds = new Bounds(53.56, 13.25, 53.57, 13.26);
Rendering visitor = new StyledMapRenderer(g, nc, false);
nc.zoomTo(bounds);
visitor.render(dsCity2, true, bounds);
IdentityHashMap<StyleElementList, Integer> counter = new IdentityHashMap<>();
int noPrimitives = 0;
for (OsmPrimitive osm : dsCity2.allPrimitives()) {
// primitives, that have been rendered, should have the cache populated
if (osm.mappaintStyle != null) {
noPrimitives++;
Pair<StyleElementList, Range> p = osm.mappaintStyle.getWithRange(nc.getDist100Pixel(), false);
StyleElementList sel = p.a;
Assert.assertNotNull(sel);
Integer k = counter.get(sel);
if (k == null) {
k = 0;
}
counter.put(sel, k + 1);
}
}
int EXPECTED_NO_PRIMITIVES = 4294; // needs to be updated if data file or bbox changes
Assert.assertEquals(
"The number of rendered primitives should be " + EXPECTED_NO_PRIMITIVES,
EXPECTED_NO_PRIMITIVES, noPrimitives);
Assert.assertTrue(
"Too many StyleElementList instances, they should be shared using the StyleCache",
counter.size() < 100);
}
}