diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..f74700d6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..8b456aa1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + push: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Set up Maven Central Repository + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'temurin' + server-id: central + server-username: OSSRH_USER + server-password: OSSRH_PASS + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + - name: Publish package + run: mvn -B -Djava.awt.headless=true deploy -P release + env: + OSSRH_USER: ${{ secrets.OSSRH_TOKEN_USER }} + OSSRH_PASS: ${{ secrets.OSSRH_TOKEN_PASSWD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + - name: Submit test coverage to Coveralls + run: mvn test jacoco:report coveralls:report -DrepoToken=${{ secrets.COVERALLS_TOKEN }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 00000000..918883b3 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,17 @@ +name: PR + +on: + pull_request: + +jobs: + build_and_test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'temurin' + - name: Build project with Maven + run: mvn -B package --file pom.xml diff --git a/.gitignore b/.gitignore index d12f9eaf..e56582dc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ nbactions.xml /store-benchmark/target/ /store/graphstore-api/target/ /store/graphstore/target/ +.idea +*.iml +.vscode/** \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d6468833..00000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -sudo: false -language: java -jdk: - - oraclejdk8 -branches: - only: - - master -cache: - directories: - - $HOME/.m2 -before_install: - - cd store - - openssl aes-256-cbc -k "$GPG_PUBRING_ENCRYPTION" -in src/travis/pubring.gpg.enc -d -a -out src/travis/pubring.gpg - - openssl aes-256-cbc -k "$GPG_SECRETRING_ENCRYPTION" -in src/travis/secretring.gpg.enc -d -a -out src/travis/secretring.gpg -install: - - echo "ossrh\${env.OSSRH_USER}\${env.OSSRH_PASS}deploymenttrue\${env.GPG_PASSPHRASE}" > ~/settings.xml -script: - - mvn --settings ~/settings.xml -Djava.awt.headless=true -Dgpg.defaultKeyring=false -Dgpg-keyname=1481F619 -Dgpg.publicKeyring=src/travis/pubring.gpg -Dgpg.secretKeyring=src/travis/secretring.gpg clean deploy -P release -after_success: - - mvn clean test jacoco:report coveralls:report diff --git a/store/LICENSE.txt b/LICENSE.txt similarity index 100% rename from store/LICENSE.txt rename to LICENSE.txt diff --git a/README.md b/README.md index eb17e509..a979958d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # GraphStore -[![Build Status](https://travis-ci.org/gephi/graphstore.svg?branch=master)](https://travis-ci.org/gephi/graphstore) +[![build](https://github.com/gephi/graphstore/actions/workflows/ci.yml/badge.svg)](https://github.com/gephi/graphstore/actions/workflows/ci.yml) +[![Apache License, Version 2.0, January 2004](https://img.shields.io/github/license/apache/maven.svg?label=License)](https://www.apache.org/licenses/LICENSE-2.0) +[![Maven Central](https://img.shields.io/maven-central/v/org.gephi/graphstore.svg?label=Maven%20Central)](https://search.maven.org/artifact/org.gephi/graphstore) [![Coverage Status](https://coveralls.io/repos/gephi/graphstore/badge.svg?branch=master&service=github)](https://coveralls.io/github/gephi/graphstore?branch=master) -GraphStore is an in-memory graph structure implementation written in Java. It is designed to be powerful, efficient and robust. It's powering the Gephi software and supports large graphs in intensive applications. +GraphStore is an in-memory graph structure implementation written in Java. It's designed to be powerful, efficient and robust. It's powering the Gephi software and supports large graphs in intensive applications. ## Features Highlight @@ -17,18 +19,40 @@ GraphStore is an in-memory graph structure implementation written in Java. It is * Supports dynamic graphs (graphs over time) * Built-in index on attribute values * Fast and compact binary serialization +* Spatial indexing based on a quadtree ## Download Stable releases can be found on [Maven central](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.gephi%22%20AND%20a%3A%22graphstore%22). +Development builds can be found on Maven's snapshot repository. ## Documentation -API Documentation is available [here](http://gephi.github.com/graphstore/apidocs/index.html). +API Documentation is available [here](https://www.javadoc.io/doc/org.gephi/graphstore/latest/index.html). + +Follow [this QuickStart](https://github.com/gephi/graphstore/wiki/Quick-Start) to get started. + +## Usage + +### From a Maven project + +```xml + + org.gephi + graphstore + 0.8.7 + +``` + +### From a Gradle project + +``` +compile 'org.gephi:graphstore:0.8.7' +``` ## Dependencies -GraphStore depends on FastUtil >= 6.0, Colt 1.2.0 and Joda-Time 2.2. +GraphStore is built for JRE 17+ and depends on FastUtil. For a complete list of dependencies, consult the `pom.xml` file. @@ -38,8 +62,9 @@ For a complete list of dependencies, consult the `pom.xml` file. GraphStore uses Maven for building. - > cd store > mvn clean install + +Note that code formatting is automatically applied at that time. ### How to test diff --git a/store/formatter-config.xml b/formatter-config.xml similarity index 99% rename from store/formatter-config.xml rename to formatter-config.xml index f2e5930d..bedc4709 100644 --- a/store/formatter-config.xml +++ b/formatter-config.xml @@ -64,7 +64,7 @@ - + diff --git a/store/pom.xml b/pom.xml similarity index 64% rename from store/pom.xml rename to pom.xml index 5812b4e5..84918177 100644 --- a/store/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.gephi graphstore - 0.6.1-SNAPSHOT + 0.8.8-SNAPSHOT jar GraphStore @@ -50,8 +50,8 @@ UTF-8 UTF-8 - 1.8 - 1.8 + 17 + 17 github @@ -59,23 +59,13 @@ org.testng testng - 6.14.3 + 7.12.0 test it.unimi.dsi fastutil - 8.3.0 - - - colt - colt - 1.2.0 - - - joda-time - joda-time - 2.10.3 + 8.5.19 @@ -85,65 +75,67 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.15.0 org.apache.maven.plugins maven-surefire-plugin - 2.22.2 - - false - + 3.5.6 org.apache.maven.plugins maven-source-plugin - 3.1.0 + 3.4.0 org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.12.0 org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.2.8 - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 + org.sonatype.central + central-publishing-maven-plugin + 0.11.0 + true org.jacoco jacoco-maven-plugin - 0.8.4 + 0.8.15 org.eluder.coveralls coveralls-maven-plugin 4.3.0 + + + + javax.xml.bind + jaxb-api + 2.3.1 + + - com.github.github - site-maven-plugin - 0.12 + net.revelc.code.formatter + formatter-maven-plugin + 2.29.0 org.codehaus.mojo - animal-sniffer-maven-plugin - 1.18 - - - net.revelc.code - formatter-maven-plugin - 0.5.2 + build-helper-maven-plugin + 3.6.1 + org.apache.maven.plugins maven-compiler-plugin @@ -152,45 +144,33 @@ ${maven.compiler.target} - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar-no-fork - - - - + + org.apache.maven.plugins - maven-javadoc-plugin + maven-surefire-plugin + + false + + true + + - attach-javadocs + unit-test - jar + test + test + + false + methods + 4 + - - - org.sonatype.plugins - nexus-staging-maven-plugin - true - - ossrh - https://oss.sonatype.org/ - true - - - org.jacoco @@ -210,36 +190,17 @@ org.eluder.coveralls coveralls-maven-plugin - - - - org.codehaus.mojo - animal-sniffer-maven-plugin - - - org.codehaus.mojo.signature - java18 - 1.0 - - - - - check-java-api - test - - check - - - - - net.revelc.code + net.revelc.code.formatter formatter-maven-plugin ${project.basedir}/formatter-config.xml - true + + ${project.build.sourceDirectory} + ${project.build.testSourceDirectory} + @@ -249,6 +210,17 @@ + + + + org.sonatype.central + central-publishing-maven-plugin + true + + central + true + + @@ -258,37 +230,39 @@ release - + org.apache.maven.plugins - maven-gpg-plugin + maven-source-plugin - sign-artifacts - verify + attach-sources - sign + jar-no-fork - + org.apache.maven.plugins - maven-site-plugin + maven-gpg-plugin - default-site - site + sign-artifacts + verify - site + sign - - true - + + + --pinentry-mode + loopback + + @@ -298,6 +272,14 @@ aggregate + + + attach-javadocs + + jar + + + public GraphStore ${project.version} API Index @@ -305,35 +287,12 @@ true true true + none + 17 - - - - com.github.github - site-maven-plugin - - Creating site for ${project.version} - - - - - site - - site - - - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - diff --git a/store/src/main/java/org/gephi/graph/api/AttributeUtils.java b/src/main/java/org/gephi/graph/api/AttributeUtils.java similarity index 69% rename from store/src/main/java/org/gephi/graph/api/AttributeUtils.java rename to src/main/java/org/gephi/graph/api/AttributeUtils.java index 520164e1..153eee6d 100644 --- a/store/src/main/java/org/gephi/graph/api/AttributeUtils.java +++ b/src/main/java/org/gephi/graph/api/AttributeUtils.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.gephi.graph.api; import it.unimi.dsi.fastutil.booleans.BooleanArrayList; @@ -41,28 +42,23 @@ import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; import it.unimi.dsi.fastutil.shorts.ShortArrayList; import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; -import org.gephi.graph.impl.TimestampsParser; -import org.gephi.graph.impl.IntervalsParser; -import org.gephi.graph.impl.FormattingAndParsingUtils; -import org.gephi.graph.api.types.TimestampMap; -import org.gephi.graph.api.types.TimestampShortMap; -import org.gephi.graph.api.types.TimestampLongMap; -import org.gephi.graph.api.types.TimestampSet; -import org.gephi.graph.api.types.TimestampCharMap; -import org.gephi.graph.api.types.TimestampDoubleMap; -import org.gephi.graph.api.types.TimestampBooleanMap; -import org.gephi.graph.api.types.TimestampFloatMap; -import org.gephi.graph.api.types.TimestampStringMap; -import org.gephi.graph.api.types.TimestampByteMap; -import org.gephi.graph.api.types.TimestampIntegerMap; import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -80,19 +76,29 @@ import org.gephi.graph.api.types.IntervalStringMap; import org.gephi.graph.api.types.TimeMap; import org.gephi.graph.api.types.TimeSet; +import org.gephi.graph.api.types.TimestampBooleanMap; +import org.gephi.graph.api.types.TimestampByteMap; +import org.gephi.graph.api.types.TimestampCharMap; +import org.gephi.graph.api.types.TimestampDoubleMap; +import org.gephi.graph.api.types.TimestampFloatMap; +import org.gephi.graph.api.types.TimestampIntegerMap; +import org.gephi.graph.api.types.TimestampLongMap; +import org.gephi.graph.api.types.TimestampMap; +import org.gephi.graph.api.types.TimestampSet; +import org.gephi.graph.api.types.TimestampShortMap; +import org.gephi.graph.api.types.TimestampStringMap; import org.gephi.graph.impl.ArraysParser; +import org.gephi.graph.impl.FormattingAndParsingUtils; import org.gephi.graph.impl.GraphStoreConfiguration; -import org.joda.time.DateTimeZone; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.ISODateTimeFormat; +import org.gephi.graph.impl.IntervalsParser; +import org.gephi.graph.impl.TimestampsParser; /** * Set of utility methods to manipulate supported attribute types. *

- * The attribute system is built with a set of supported column types. This - * class contains utilities to parse and convert supported types. It also - * contains utilities to manipulate primitive arrays (the preferred array type) - * and date/time types. Default time zone for parsing/printing dates is UTC. + * The attribute system is built with a set of supported column types. This class contains utilities to parse and + * convert supported types. It also contains utilities to manipulate primitive arrays (the preferred array type) and + * date/time types. Default time zone for parsing/printing dates is UTC. */ public class AttributeUtils { @@ -105,11 +111,11 @@ public class AttributeUtils { // These are used to avoid creating a lot of new instances of // DateTimeFormatter - private static final Map DATE_PRINTERS_BY_TIMEZONE; - private static final Map DATE_TIME_PRINTERS_BY_TIMEZONE; - private static final Map DATE_TIME_PARSERS_BY_TIMEZONE; + private static final Map DATE_PRINTERS_BY_TIMEZONE; + private static final Map DATE_TIME_PRINTERS_BY_TIMEZONE; + private static final Map DATE_TIME_PARSERS_BY_TIMEZONE; - // Collectio types to speedup lookup + // Collection types to speedup lookup private static final Set TYPED_LIST_TYPES; private static final Set TYPED_SET_TYPES; private static final Set TYPED_MAP_TYPES; @@ -140,6 +146,9 @@ public class AttributeUtils { // Objects supportedTypes.add(String.class); + // Instant + supportedTypes.add(Instant.class); + // Primitives Array supportedTypes.add(Boolean[].class); supportedTypes.add(boolean[].class); @@ -220,14 +229,30 @@ public class AttributeUtils { TYPES_STANDARDIZATION = Collections.unmodifiableMap(typesStandardization); // Datetime - make sure UTC timezone is used by default - DATE_TIME_PARSER = ISODateTimeFormat.dateOptionalTimeParser() + DATE_TIME_PARSER = new DateTimeFormatterBuilder().parseCaseInsensitive() + .appendOptional(DateTimeFormatter.ISO_DATE).appendOptional(DateTimeFormatter.ofPattern("yyyyMMdd")) + .optionalStart().appendLiteral('T').append(DateTimeFormatter.ISO_TIME) + .appendPattern("[.SSSSSSSSS][.SSSSSS][.SSS]").optionalEnd().optionalStart() + .appendFraction(ChronoField.NANO_OF_SECOND, 9, 9, true).optionalEnd() + // optional nanos with 6 digits (including decimal point) + .optionalStart().appendFraction(ChronoField.NANO_OF_SECOND, 6, 6, true).optionalEnd() + // optional nanos with 3 digits (including decimal point) + .optionalStart().appendFraction(ChronoField.NANO_OF_SECOND, 3, 3, true).optionalEnd() + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0).parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0).parseDefaulting(ChronoField.NANO_OF_SECOND, 0) + .toFormatter().withZone(GraphStoreConfiguration.DEFAULT_TIME_ZONE); + DATE_PRINTER = new DateTimeFormatterBuilder().parseCaseInsensitive().appendPattern("yyyy-MM-dd").toFormatter() + .withZone(GraphStoreConfiguration.DEFAULT_TIME_ZONE); + DATE_TIME_PRINTER = new DateTimeFormatterBuilder().parseCaseInsensitive() + .append(DateTimeFormatter.ISO_LOCAL_DATE).appendLiteral('T').appendPattern("HH:mm:ss") + .appendPattern(".SSS").parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0).parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .parseDefaulting(ChronoField.NANO_OF_SECOND, 0).appendOffset("+HH:MM", "Z").toFormatter() .withZone(GraphStoreConfiguration.DEFAULT_TIME_ZONE); - DATE_PRINTER = ISODateTimeFormat.date().withZone(GraphStoreConfiguration.DEFAULT_TIME_ZONE); - DATE_TIME_PRINTER = ISODateTimeFormat.dateTime().withZone(GraphStoreConfiguration.DEFAULT_TIME_ZONE); - DATE_PRINTERS_BY_TIMEZONE = new HashMap<>(); - DATE_TIME_PRINTERS_BY_TIMEZONE = new HashMap<>(); - DATE_TIME_PARSERS_BY_TIMEZONE = new HashMap<>(); + DATE_PRINTERS_BY_TIMEZONE = new ConcurrentHashMap<>(); + DATE_TIME_PRINTERS_BY_TIMEZONE = new ConcurrentHashMap<>(); + DATE_TIME_PARSERS_BY_TIMEZONE = new ConcurrentHashMap<>(); DATE_PRINTERS_BY_TIMEZONE.put(DATE_PRINTER.getZone(), DATE_PRINTER); DATE_TIME_PRINTERS_BY_TIMEZONE.put(DATE_TIME_PRINTER.getZone(), DATE_TIME_PRINTER); @@ -275,30 +300,24 @@ private AttributeUtils() { // Only static methods } - private static DateTimeFormatter getDateTimeFormatterByTimeZone(Map cache, DateTimeFormatter baseFormatter, DateTimeZone timeZone) { - if (timeZone == null) { + private static DateTimeFormatter getDateTimeFormatterByTimeZone(Map cache, DateTimeFormatter baseFormatter, ZoneId zoneId) { + if (zoneId == null) { return baseFormatter; } - DateTimeFormatter formatter = cache.get(timeZone); - if (formatter == null) { - formatter = baseFormatter.withZone(timeZone); - cache.put(timeZone, formatter); - } - - return formatter; + return cache.computeIfAbsent(zoneId, z -> baseFormatter.withZone(z)); } - private static DateTimeFormatter getDateTimeParserByTimeZone(DateTimeZone timeZone) { - return getDateTimeFormatterByTimeZone(DATE_TIME_PARSERS_BY_TIMEZONE, DATE_TIME_PARSER, timeZone); + private static DateTimeFormatter getDateTimeParserByTimeZone(ZoneId zoneId) { + return getDateTimeFormatterByTimeZone(DATE_TIME_PARSERS_BY_TIMEZONE, DATE_TIME_PARSER, zoneId); } - private static DateTimeFormatter getDateTimePrinterByTimeZone(DateTimeZone timeZone) { - return getDateTimeFormatterByTimeZone(DATE_TIME_PRINTERS_BY_TIMEZONE, DATE_TIME_PRINTER, timeZone); + private static DateTimeFormatter getDateTimePrinterByTimeZone(ZoneId zoneId) { + return getDateTimeFormatterByTimeZone(DATE_TIME_PRINTERS_BY_TIMEZONE, DATE_TIME_PRINTER, zoneId); } - private static DateTimeFormatter getDatePrinterByTimeZone(DateTimeZone timeZone) { - return getDateTimeFormatterByTimeZone(DATE_PRINTERS_BY_TIMEZONE, DATE_PRINTER, timeZone); + private static DateTimeFormatter getDatePrinterByTimeZone(ZoneId zoneId) { + return getDateTimeFormatterByTimeZone(DATE_PRINTERS_BY_TIMEZONE, DATE_PRINTER, zoneId); } /** @@ -316,18 +335,21 @@ public static String print(Object value) { * * @param value value * @param timeFormat time format - * @param timeZone time zone + * @param zoneId time zone * @return string representation */ - public static String print(Object value, TimeFormat timeFormat, DateTimeZone timeZone) { + public static String print(Object value, TimeFormat timeFormat, ZoneId zoneId) { if (value == null) { return "null"; } if (value instanceof TimeSet) { - return ((TimeSet) value).toString(timeFormat, timeZone); + return ((TimeSet) value).toString(timeFormat, zoneId); } if (value instanceof TimeMap) { - return ((TimeMap) value).toString(timeFormat, timeZone); + return ((TimeMap) value).toString(timeFormat, zoneId); + } + if (value instanceof Instant) { + return value.toString(); } if (value.getClass().isArray()) { return printArray(value); @@ -336,21 +358,23 @@ public static String print(Object value, TimeFormat timeFormat, DateTimeZone tim } /** - * Parses the given string using the type class provided and returns an - * instance. + * Parses the given string using the type class provided and returns an instance. * * @param str string to parse * @param typeClass class of the desired type - * @param timeZone time zone to use or null to use default time zone (UTC), - * for dynamic types only - * @return an instance of the type class, or null if str is null or - * empty + * @param zoneId time zone to use or null to use default time zone (UTC), for dynamic types and Instant + * only + * @return an instance of the type class, or null if str is null or empty */ - public static Object parse(String str, Class typeClass, DateTimeZone timeZone) { + public static Object parse(String str, Class typeClass, ZoneId zoneId) { if (str == null || str.isEmpty()) { return null; } + if (str.equalsIgnoreCase("null")) { + return null; + } + if (typeClass.isPrimitive()) { typeClass = getStandardizedType(typeClass);// For primitives we can // use auto-unboxing @@ -360,17 +384,17 @@ public static Object parse(String str, Class typeClass, DateTimeZone timeZone) { if (typeClass.equals(String.class)) { return str; } else if (typeClass.equals(Byte.class)) { - return new Byte(str); + return Byte.valueOf(str); } else if (typeClass.equals(Short.class)) { - return new Short(str); + return Short.valueOf(str); } else if (typeClass.equals(Integer.class)) { - return new Integer(str); + return Integer.valueOf(str); } else if (typeClass.equals(Long.class)) { - return new Long(str); + return Long.valueOf(str); } else if (typeClass.equals(Float.class)) { - return new Float(str); + return Float.valueOf(str); } else if (typeClass.equals(Double.class)) { - return new Double(str); + return Double.valueOf(str); } else if (typeClass.equals(BigInteger.class)) { return new BigInteger(str); } else if (typeClass.equals(BigDecimal.class)) { @@ -391,50 +415,56 @@ public static Object parse(String str, Class typeClass, DateTimeZone timeZone) { return str.charAt(0); } + // Instant + if (typeClass.equals(Instant.class)) { + double milliseconds = FormattingAndParsingUtils.parseDateTimeOrTimestamp(str, zoneId); + return Instant.ofEpochMilli(Math.round(milliseconds)); + } + // Interval types: if (typeClass.equals(IntervalSet.class)) { - return IntervalsParser.parseIntervalSet(str, timeZone); + return IntervalsParser.parseIntervalSet(str, zoneId); } else if (typeClass.equals(IntervalStringMap.class)) { - return IntervalsParser.parseIntervalMap(String.class, str, timeZone); + return IntervalsParser.parseIntervalMap(String.class, str, zoneId); } else if (typeClass.equals(IntervalByteMap.class)) { - return IntervalsParser.parseIntervalMap(Byte.class, str, timeZone); + return IntervalsParser.parseIntervalMap(Byte.class, str, zoneId); } else if (typeClass.equals(IntervalShortMap.class)) { - return IntervalsParser.parseIntervalMap(Short.class, str, timeZone); + return IntervalsParser.parseIntervalMap(Short.class, str, zoneId); } else if (typeClass.equals(IntervalIntegerMap.class)) { - return IntervalsParser.parseIntervalMap(Integer.class, str, timeZone); + return IntervalsParser.parseIntervalMap(Integer.class, str, zoneId); } else if (typeClass.equals(IntervalLongMap.class)) { - return IntervalsParser.parseIntervalMap(Long.class, str, timeZone); + return IntervalsParser.parseIntervalMap(Long.class, str, zoneId); } else if (typeClass.equals(IntervalFloatMap.class)) { - return IntervalsParser.parseIntervalMap(Float.class, str, timeZone); + return IntervalsParser.parseIntervalMap(Float.class, str, zoneId); } else if (typeClass.equals(IntervalDoubleMap.class)) { - return IntervalsParser.parseIntervalMap(Double.class, str, timeZone); + return IntervalsParser.parseIntervalMap(Double.class, str, zoneId); } else if (typeClass.equals(IntervalBooleanMap.class)) { - return IntervalsParser.parseIntervalMap(Boolean.class, str, timeZone); + return IntervalsParser.parseIntervalMap(Boolean.class, str, zoneId); } else if (typeClass.equals(IntervalCharMap.class)) { - return IntervalsParser.parseIntervalMap(Character.class, str, timeZone); + return IntervalsParser.parseIntervalMap(Character.class, str, zoneId); } // Timestamp types: if (typeClass.equals(TimestampSet.class)) { - return TimestampsParser.parseTimestampSet(str, timeZone); + return TimestampsParser.parseTimestampSet(str, zoneId); } else if (typeClass.equals(TimestampStringMap.class)) { - return TimestampsParser.parseTimestampMap(String.class, str, timeZone); + return TimestampsParser.parseTimestampMap(String.class, str, zoneId); } else if (typeClass.equals(TimestampByteMap.class)) { - return TimestampsParser.parseTimestampMap(Byte.class, str, timeZone); + return TimestampsParser.parseTimestampMap(Byte.class, str, zoneId); } else if (typeClass.equals(TimestampShortMap.class)) { - return TimestampsParser.parseTimestampMap(Short.class, str, timeZone); + return TimestampsParser.parseTimestampMap(Short.class, str, zoneId); } else if (typeClass.equals(TimestampIntegerMap.class)) { - return TimestampsParser.parseTimestampMap(Integer.class, str, timeZone); + return TimestampsParser.parseTimestampMap(Integer.class, str, zoneId); } else if (typeClass.equals(TimestampLongMap.class)) { - return TimestampsParser.parseTimestampMap(Long.class, str, timeZone); + return TimestampsParser.parseTimestampMap(Long.class, str, zoneId); } else if (typeClass.equals(TimestampFloatMap.class)) { - return TimestampsParser.parseTimestampMap(Float.class, str, timeZone); + return TimestampsParser.parseTimestampMap(Float.class, str, zoneId); } else if (typeClass.equals(TimestampDoubleMap.class)) { - return TimestampsParser.parseTimestampMap(Double.class, str, timeZone); + return TimestampsParser.parseTimestampMap(Double.class, str, zoneId); } else if (typeClass.equals(TimestampBooleanMap.class)) { - return TimestampsParser.parseTimestampMap(Boolean.class, str, timeZone); + return TimestampsParser.parseTimestampMap(Boolean.class, str, zoneId); } else if (typeClass.equals(TimestampCharMap.class)) { - return TimestampsParser.parseTimestampMap(Character.class, str, timeZone); + return TimestampsParser.parseTimestampMap(Character.class, str, zoneId); } // Array types: @@ -455,25 +485,25 @@ public static Object parse(String str, Class typeClass, DateTimeZone timeZone) { } else if (typeClass.equals(double[].class)) { return ArraysParser.parseArrayAsPrimitiveArray(Double[].class, str); } else if (typeClass.equals(Boolean[].class) || typeClass.equals(String[].class) || typeClass - .equals(Character[].class) || typeClass.equals(Byte[].class) || typeClass.equals(Short[].class) || typeClass - .equals(Integer[].class) || typeClass.equals(Long[].class) || typeClass.equals(Float[].class) || typeClass - .equals(Double[].class) || typeClass.equals(BigInteger[].class) || typeClass.equals(BigDecimal[].class)) { + .equals(Character[].class) || typeClass.equals(Byte[].class) || typeClass + .equals(Short[].class) || typeClass.equals(Integer[].class) || typeClass + .equals(Long[].class) || typeClass.equals(Float[].class) || typeClass + .equals(Double[].class) || typeClass + .equals(BigInteger[].class) || typeClass.equals(BigDecimal[].class)) { return ArraysParser.parseArray(typeClass, str); } - throw new IllegalArgumentException("Unsupported type " + typeClass.getClass().getCanonicalName()); + throw new IllegalArgumentException("Unsupported type " + typeClass.getCanonicalName()); } /** - * Parses the given string using the type class provided and returns an - * instance. + * Parses the given string using the type class provided and returns an instance. * * Default time zone is used (UTC) for dynamic types (timestamps/intervals). * * @param str string to parse * @param typeClass class of the desired type - * @return an instance of the type class, or null if str is null or - * empty + * @return an instance of the type class, or null if str is null or empty */ public static Object parse(String str, Class typeClass) { return parse(str, typeClass, null); @@ -517,15 +547,15 @@ public static Class getPrimitiveType(Class type) { * * @param array wrapped primitive array instance * @return primitive array instance - * @throws IllegalArgumentException Thrown if any of the array values is - * null + * @throws IllegalArgumentException Thrown if any of the array values is null */ public static Object getPrimitiveArray(Object[] array) { if (!isSupported(array.getClass())) { throw new IllegalArgumentException("Unsupported type " + array.getClass().getCanonicalName()); } Class arrayClass = array.getClass().getComponentType(); - if (!arrayClass.isPrimitive() && (arrayClass == Double.class || arrayClass == Float.class || arrayClass == Long.class || arrayClass == Integer.class || arrayClass == Short.class || arrayClass == Character.class || arrayClass == Byte.class || arrayClass == Boolean.class)) { + if (!arrayClass + .isPrimitive() && (arrayClass == Double.class || arrayClass == Float.class || arrayClass == Long.class || arrayClass == Integer.class || arrayClass == Short.class || arrayClass == Character.class || arrayClass == Byte.class || arrayClass == Boolean.class)) { Class primitiveClass = getPrimitiveType(arrayClass); int arrayLength = array.length; @@ -565,8 +595,7 @@ public static boolean isSupported(Class type) { /** * Returns the standardized type for the given type class. *

- * For instance, getStandardizedType(int.class) would return - * Integer.class. + * For instance, getStandardizedType(int.class) would return Integer.class. * * @param type type to standardize * @return standardized type @@ -698,8 +727,7 @@ public static Class getStaticType(Class type) { } /** - * Transform the given value instance in a standardized type if - * necessary. + * Transform the given value instance in a standardized type if necessary. *

* This function transforms wrapped primitive arrays in primitive arrays. * @@ -744,8 +772,7 @@ private static List getStandardizedList(List list) { } } if (oCls != null && !(isSimpleType(oCls) || isArrayType(oCls))) { - throw new IllegalArgumentException("The list contains unsupported type " + oCls.getClass() - .getCanonicalName()); + throw new IllegalArgumentException("The list contains unsupported type " + oCls.getCanonicalName()); } if (oCls != null) { if (oCls.equals(Integer.class)) { @@ -774,8 +801,8 @@ private static List getStandardizedList(List list) { } private static Set getStandardizedSet(Set set) { - Class listClass = set.getClass(); - if (TYPED_LIST_TYPES.contains(listClass)) { + Class setClass = set.getClass(); + if (TYPED_SET_TYPES.contains(setClass)) { return set; } @@ -790,8 +817,7 @@ private static Set getStandardizedSet(Set set) { } } if (oCls != null && !(isSimpleType(oCls) || isArrayType(oCls))) { - throw new IllegalArgumentException("The set contains unsupported type " + oCls.getClass() - .getCanonicalName()); + throw new IllegalArgumentException("The set contains unsupported type " + oCls.getCanonicalName()); } if (oCls != null) { if (oCls.equals(Integer.class)) { @@ -837,13 +863,12 @@ private static Map getStandardizedMap(Map map) { } } if (value != null && !(isSimpleType(value.getClass()) || isArrayType(value.getClass()))) { - throw new IllegalArgumentException("The map contains unsupported value type " + value.getClass() - .getCanonicalName()); + throw new IllegalArgumentException( + "The map contains unsupported value type " + value.getClass().getCanonicalName()); } } if (oCls != null && !isSimpleType(oCls)) { - throw new IllegalArgumentException("The map contains unsupported key type " + oCls.getClass() - .getCanonicalName()); + throw new IllegalArgumentException("The map contains unsupported key type " + oCls.getCanonicalName()); } if (oCls != null) { if (oCls.equals(Integer.class)) { @@ -883,14 +908,20 @@ public static boolean isNumberType(Class type) { } type = getStandardizedType(type); return Number.class.isAssignableFrom(type) || int[].class.isAssignableFrom(type) || float[].class - .isAssignableFrom(type) || double[].class.isAssignableFrom(type) || byte[].class.isAssignableFrom(type) || short[].class - .isAssignableFrom(type) || long[].class.isAssignableFrom(type) || type - .equals(TimestampIntegerMap.class) || type.equals(TimestampFloatMap.class) || type - .equals(TimestampDoubleMap.class) || type.equals(TimestampLongMap.class) || type - .equals(TimestampShortMap.class) || type.equals(TimestampByteMap.class) || type - .equals(IntervalIntegerMap.class) || type.equals(IntervalFloatMap.class) || type - .equals(IntervalDoubleMap.class) || type.equals(IntervalLongMap.class) || type - .equals(IntervalShortMap.class) || type.equals(IntervalByteMap.class); + .isAssignableFrom(type) || double[].class.isAssignableFrom(type) || byte[].class + .isAssignableFrom(type) || short[].class.isAssignableFrom(type) || long[].class + .isAssignableFrom(type) || type.equals(TimestampIntegerMap.class) || type + .equals(TimestampFloatMap.class) || type + .equals(TimestampDoubleMap.class) || type + .equals(TimestampLongMap.class) || type + .equals(TimestampShortMap.class) || type + .equals(TimestampByteMap.class) || type + .equals(IntervalIntegerMap.class) || type + .equals(IntervalFloatMap.class) || type + .equals(IntervalDoubleMap.class) || type + .equals(IntervalLongMap.class) || type + .equals(IntervalShortMap.class) || type + .equals(IntervalByteMap.class); } /** @@ -922,8 +953,8 @@ public static boolean isBooleanType(Class type) { throw new IllegalArgumentException("Unsupported type " + type.getCanonicalName()); } type = getStandardizedType(type); - return type.equals(Boolean.class) || type.equals(boolean[].class) || type.equals(TimestampBooleanMap.class) || type - .equals(IntervalBooleanMap.class); + return type.equals(Boolean.class) || type.equals(boolean[].class) || type + .equals(TimestampBooleanMap.class) || type.equals(IntervalBooleanMap.class); } /** @@ -935,7 +966,7 @@ public static boolean isBooleanType(Class type) { public static boolean isDynamicType(Class type) { return (!type.equals(TimestampMap.class) && TimestampMap.class.isAssignableFrom(type)) || type .equals(TimestampSet.class) || (!type.equals(IntervalMap.class) && IntervalMap.class - .isAssignableFrom(type)) || type.equals(IntervalSet.class); + .isAssignableFrom(type)) || type.equals(IntervalSet.class); } /** @@ -947,7 +978,8 @@ public static boolean isDynamicType(Class type) { * @return true if type is a simple type, false otherwise */ public static boolean isSimpleType(Class type) { - return (type.isPrimitive() && type != void.class) || type == Double.class || type == Float.class || type == Long.class || type == Integer.class || type == Short.class || type == Character.class || type == Byte.class || type == Boolean.class || type == String.class; + return (type + .isPrimitive() && type != void.class) || type == Double.class || type == Float.class || type == Long.class || type == Integer.class || type == Short.class || type == Character.class || type == Byte.class || type == Boolean.class || type == String.class; } /** @@ -1002,46 +1034,50 @@ public static String getTypeName(Class type) { * Parses the given time and returns its milliseconds representation. * * @param dateTime type to parse - * @param timeZone time zone to use or null to use default time zone (UTC) + * @param zoneId time zone to use or null to use default time zone (UTC) * @return milliseconds representation + * @throws DateTimeParseException if the time cannot be parsed */ - public static double parseDateTime(String dateTime, DateTimeZone timeZone) { - return getDateTimeParserByTimeZone(timeZone).parseDateTime(dateTime).getMillis(); + public static double parseDateTime(String dateTime, ZoneId zoneId) throws DateTimeParseException { + DateTimeFormatter dateTimeParserByTimeZone = getDateTimeParserByTimeZone(zoneId); + Instant instant = dateTimeParserByTimeZone.parse(dateTime, Instant::from); + return (double) instant.toEpochMilli(); } /** - * Parses the given time and returns its milliseconds representation. - * Default time zone is used (UTC). + * Parses the given time and returns its milliseconds representation. Default time zone is used (UTC). * * @param dateTime the type to parse * @return milliseconds representation + * @throws DateTimeParseException if the time cannot be parsed */ - public static double parseDateTime(String dateTime) { + public static double parseDateTime(String dateTime) throws DateTimeParseException { return parseDateTime(dateTime, null); } /** - * Parses an ISO date with or without time or a timestamp (in milliseconds). - * Returns the date or timestamp converted to a timestamp in milliseconds. + * Parses an ISO date with or without time or a timestamp (in milliseconds). Returns the date or timestamp converted + * to a timestamp in milliseconds. * * @param timeStr Date or timestamp string - * @param timeZone Time zone to use or null to use default time zone (UTC) + * @param zoneId Time zone to use or null to use default time zone (UTC) * @return Timestamp + * @throws DateTimeParseException if the time cannot be parsed */ - public static double parseDateTimeOrTimestamp(String timeStr, DateTimeZone timeZone) { - return FormattingAndParsingUtils.parseDateTimeOrTimestamp(timeStr, timeZone); + public static double parseDateTimeOrTimestamp(String timeStr, ZoneId zoneId) throws DateTimeParseException { + return FormattingAndParsingUtils.parseDateTimeOrTimestamp(timeStr, zoneId); } /** - * Parses an ISO date with or without time or a timestamp (in milliseconds). - * Returns the date or timestamp converted to a timestamp in milliseconds. - * Default time zone is used (UTC). + * Parses an ISO date with or without time or a timestamp (in milliseconds). Returns the date or timestamp converted + * to a timestamp in milliseconds. Default time zone is used (UTC). * * @param timeStr Date or timestamp string * @return Timestamp + * @throws DateTimeParseException if the time cannot be parsed */ - public static double parseDateTimeOrTimestamp(String timeStr) { - return FormattingAndParsingUtils.parseDateTimeOrTimestamp(timeStr, null); + public static double parseDateTimeOrTimestamp(String timeStr) throws DateTimeParseException { + return FormattingAndParsingUtils.parseDateTimeOrTimestamp(timeStr); } /** @@ -1058,19 +1094,31 @@ public static String printTimestamp(double timestamp) { * Returns the date's string representation of the given timestamp. * * @param timestamp time, in milliseconds - * @param timeZone time zone to use or null to use default time zone (UTC) + * @param zoneId time zone to use or null to use default time zone (UTC) * @return formatted date */ - public static String printDate(double timestamp, DateTimeZone timeZone) { + public static String printDate(double timestamp, ZoneId zoneId) { if (Double.isInfinite(timestamp) || Double.isNaN(timestamp)) { return printTimestamp(timestamp); } - return getDatePrinterByTimeZone(timeZone).print((long) timestamp); + return printDate(Instant.ofEpochMilli((long) timestamp), zoneId); + } + + /** + * Returns the date's string representation of the given instant. + * + * @param instant instant to format + * @param zoneId time zone to use or null to use default time zone (UTC) + * @return formatted date + */ + public static String printDate(Instant instant, ZoneId zoneId) { + DateTimeFormatter datePrinterByTimeZone = getDatePrinterByTimeZone(zoneId); + ZonedDateTime zonedDateTime = instant.atZone(datePrinterByTimeZone.getZone()); + return zonedDateTime.format(datePrinterByTimeZone); } /** - * Returns the date's string representation of the given timestamp. Default - * time zone is used (UTC). + * Returns the date's string representation of the given timestamp. Default time zone is used (UTC). * * @param timestamp time, in milliseconds * @return formatted date @@ -1083,19 +1131,32 @@ public static String printDate(double timestamp) { * Returns the time's string representation of the given timestamp. * * @param timestamp time, in milliseconds - * @param timeZone time zone to use or null to use default time zone (UTC) + * @param zoneId time zone to use or null to use default time zone (UTC) * @return formatted time */ - public static String printDateTime(double timestamp, DateTimeZone timeZone) { + public static String printDateTime(double timestamp, ZoneId zoneId) { if (Double.isInfinite(timestamp) || Double.isNaN(timestamp)) { return printTimestamp(timestamp); } - return getDateTimePrinterByTimeZone(timeZone).print((long) timestamp); + return printDateTime(Instant.ofEpochMilli((long) timestamp), zoneId); } /** - * Returns the time's tring representation of the given timestamp. Default - * time zone is used (UTC). + * Returns the time's string representation of the given instant. + * + * @param instant instant to format + * @param zoneId time zone to use or null to use default time zone (UTC) + * @return formatted time + */ + public static String printDateTime(Instant instant, ZoneId zoneId) { + DateTimeFormatter dateTimePrinterByTimeZone = getDateTimePrinterByTimeZone(zoneId); + ZonedDateTime zonedDateTime2 = instant.atZone(dateTimePrinterByTimeZone.getZone()); + OffsetDateTime time = OffsetDateTime.from(zonedDateTime2); + return time.format(dateTimePrinterByTimeZone); + } + + /** + * Returns the time's string representation of the given timestamp. Default time zone is used (UTC). * * @param timestamp time, in milliseconds * @return formatted time @@ -1105,20 +1166,19 @@ public static String printDateTime(double timestamp) { } /** - * Returns the string representation of the given timestamp in the given - * format. + * Returns the string representation of the given timestamp in the given format. * * @param timestamp time, in milliseconds * @param timeFormat time format - * @param timeZone time zone to use or null to use default time zone (UTC). + * @param zoneId time zone to use or null to use default time zone (UTC). * @return formatted timestamp */ - public static String printTimestampInFormat(double timestamp, TimeFormat timeFormat, DateTimeZone timeZone) { + public static String printTimestampInFormat(double timestamp, TimeFormat timeFormat, ZoneId zoneId) { switch (timeFormat) { case DATE: - return AttributeUtils.printDate(timestamp, timeZone); + return AttributeUtils.printDate(timestamp, zoneId); case DATETIME: - return AttributeUtils.printDateTime(timestamp, timeZone); + return AttributeUtils.printDateTime(timestamp, zoneId); case DOUBLE: return AttributeUtils.printTimestamp(timestamp); } @@ -1127,8 +1187,7 @@ public static String printTimestampInFormat(double timestamp, TimeFormat timeFor } /** - * Returns the string representation of the given timestamp in the given - * format. Default time zone is used (UTC). + * Returns the string representation of the given timestamp in the given format. Default time zone is used (UTC). * * @param timestamp time, in milliseconds * @param timeFormat time format @@ -1139,8 +1198,7 @@ public static String printTimestampInFormat(double timestamp, TimeFormat timeFor } /** - * Returns the string representation of the given array. The used format is - * the same format supported by + * Returns the string representation of the given array. The used format is the same format supported by * {@link #parse(java.lang.String, java.lang.Class)} method * * @param arr Input array. Can be an array of objects or primitives. @@ -1169,4 +1227,152 @@ public static boolean isNodeColumn(Column colum) { public static boolean isEdgeColumn(Column colum) { return colum.getTable().getElementClass().equals(Edge.class); } + + /** + * Returns a copy of the provided object. + *

+ * The copy is a deep copy for arrays, {@link IntervalSet}, {@link TimestampSet}, sets and lists + * + * @param obj object to copy + * @return copy of the provided object + */ + public static Object copy(Object obj) { + if (obj == null) { + return null; + } + Class typeClass = obj.getClass(); + if (!isSupported(typeClass)) { + throw new IllegalArgumentException("Unsupported type " + typeClass.getCanonicalName()); + } + typeClass = getStandardizedType(typeClass); + obj = standardizeValue(obj); + + // Primitive + if (isSimpleType(typeClass)) { + return obj; + } + + // Instant + if (typeClass.equals(Instant.class)) { + return obj; + } + + // Interval types: + if (typeClass.equals(IntervalSet.class)) { + return new IntervalSet((IntervalSet) obj); + } else if (typeClass.equals(IntervalStringMap.class)) { + return new IntervalStringMap((IntervalStringMap) obj); + } else if (typeClass.equals(IntervalByteMap.class)) { + return new IntervalByteMap((IntervalByteMap) obj); + } else if (typeClass.equals(IntervalShortMap.class)) { + return new IntervalShortMap((IntervalShortMap) obj); + } else if (typeClass.equals(IntervalIntegerMap.class)) { + return new IntervalIntegerMap((IntervalIntegerMap) obj); + } else if (typeClass.equals(IntervalLongMap.class)) { + return new IntervalLongMap((IntervalLongMap) obj); + } else if (typeClass.equals(IntervalFloatMap.class)) { + return new IntervalFloatMap((IntervalFloatMap) obj); + } else if (typeClass.equals(IntervalDoubleMap.class)) { + return new IntervalDoubleMap((IntervalDoubleMap) obj); + } else if (typeClass.equals(IntervalBooleanMap.class)) { + return new IntervalBooleanMap((IntervalBooleanMap) obj); + } else if (typeClass.equals(IntervalCharMap.class)) { + return new IntervalCharMap((IntervalCharMap) obj); + } + + // Timestamp types: + if (typeClass.equals(TimestampSet.class)) { + return new TimestampSet((TimestampSet) obj); + } else if (typeClass.equals(TimestampStringMap.class)) { + return new TimestampStringMap((TimestampStringMap) obj); + } else if (typeClass.equals(TimestampByteMap.class)) { + return new TimestampByteMap((TimestampByteMap) obj); + } else if (typeClass.equals(TimestampShortMap.class)) { + return new TimestampShortMap((TimestampShortMap) obj); + } else if (typeClass.equals(TimestampIntegerMap.class)) { + return new TimestampIntegerMap((TimestampIntegerMap) obj); + } else if (typeClass.equals(TimestampLongMap.class)) { + return new TimestampLongMap((TimestampLongMap) obj); + } else if (typeClass.equals(TimestampFloatMap.class)) { + return new TimestampFloatMap((TimestampFloatMap) obj); + } else if (typeClass.equals(TimestampDoubleMap.class)) { + return new TimestampDoubleMap((TimestampDoubleMap) obj); + } else if (typeClass.equals(TimestampBooleanMap.class)) { + return new TimestampBooleanMap((TimestampBooleanMap) obj); + } else if (typeClass.equals(TimestampCharMap.class)) { + return new TimestampCharMap((TimestampCharMap) obj); + } + + // Array + if (isArrayType(typeClass)) { + Class componentType = typeClass.getComponentType(); + int length = Array.getLength(obj); + Object dest = Array.newInstance(componentType, length); + System.arraycopy(obj, 0, dest, 0, length); + return dest; + } + + // List + if (obj instanceof CharArrayList) { + return new CharArrayList((CharArrayList) obj); + } else if (obj instanceof BooleanArrayList) { + return new BooleanArrayList((BooleanArrayList) obj); + } else if (obj instanceof ByteArrayList) { + return new ByteArrayList((ByteArrayList) obj); + } else if (obj instanceof ShortArrayList) { + return new ShortArrayList((ShortArrayList) obj); + } else if (obj instanceof IntArrayList) { + return new IntArrayList((IntArrayList) obj); + } else if (obj instanceof LongArrayList) { + return new LongArrayList((LongArrayList) obj); + } else if (obj instanceof FloatArrayList) { + return new FloatArrayList((FloatArrayList) obj); + } else if (obj instanceof DoubleArrayList) { + return new DoubleArrayList((DoubleArrayList) obj); + } else if (obj instanceof ObjectArrayList) { + return new ObjectArrayList((ObjectArrayList) obj); + } + + // Map + if (obj instanceof Char2ObjectOpenHashMap) { + return new Char2ObjectOpenHashMap((Char2ObjectOpenHashMap) obj); + } else if (obj instanceof Byte2ObjectOpenHashMap) { + return new Byte2ObjectOpenHashMap((Byte2ObjectOpenHashMap) obj); + } else if (obj instanceof Short2ObjectOpenHashMap) { + return new Short2ObjectOpenHashMap((Short2ObjectOpenHashMap) obj); + } else if (obj instanceof Int2ObjectOpenHashMap) { + return new Int2ObjectOpenHashMap((Int2ObjectOpenHashMap) obj); + } else if (obj instanceof Long2ObjectOpenHashMap) { + return new Long2ObjectOpenHashMap((Long2ObjectOpenHashMap) obj); + } else if (obj instanceof Float2ObjectOpenHashMap) { + return new Float2ObjectOpenHashMap((Float2ObjectOpenHashMap) obj); + } else if (obj instanceof Double2ObjectOpenHashMap) { + return new Double2ObjectOpenHashMap((Double2ObjectOpenHashMap) obj); + } else if (obj instanceof Object2ObjectOpenHashMap) { + return new Object2ObjectOpenHashMap((Object2ObjectOpenHashMap) obj); + } + + // Set + if (obj instanceof CharOpenHashSet) { + return new CharOpenHashSet((CharOpenHashSet) obj); + } else if (obj instanceof BooleanOpenHashSet) { + return new BooleanOpenHashSet((BooleanOpenHashSet) obj); + } else if (obj instanceof ByteOpenHashSet) { + return new ByteOpenHashSet((ByteOpenHashSet) obj); + } else if (obj instanceof ShortOpenHashSet) { + return new ShortOpenHashSet((ShortOpenHashSet) obj); + } else if (obj instanceof IntOpenHashSet) { + return new IntOpenHashSet((IntOpenHashSet) obj); + } else if (obj instanceof LongOpenHashSet) { + return new LongOpenHashSet((LongOpenHashSet) obj); + } else if (obj instanceof FloatOpenHashSet) { + return new FloatOpenHashSet((FloatOpenHashSet) obj); + } else if (obj instanceof DoubleOpenHashSet) { + return new DoubleOpenHashSet((DoubleOpenHashSet) obj); + } else if (obj instanceof ObjectOpenHashSet) { + return new ObjectOpenHashSet((ObjectOpenHashSet) obj); + } + + return obj; + } } diff --git a/store/src/main/java/org/gephi/graph/api/Column.java b/src/main/java/org/gephi/graph/api/Column.java similarity index 84% rename from store/src/main/java/org/gephi/graph/api/Column.java rename to src/main/java/org/gephi/graph/api/Column.java index 45f75e14..9d52d2ed 100644 --- a/store/src/main/java/org/gephi/graph/api/Column.java +++ b/src/main/java/org/gephi/graph/api/Column.java @@ -18,8 +18,7 @@ /** * A column belongs to a table and represent a dimension in the data. *

- * A column has primarily a unique identifier and a type, which both are set at - * the creation time. + * A column has primarily a unique identifier and a type, which both are set at the creation time. * * @see Table */ @@ -33,8 +32,7 @@ public interface Column { public String getId(); /** - * Returns the column's integer index, which is the position of the column - * in the store. + * Returns the column's integer index, which is the position of the column in the store. * * @return the column's index */ @@ -96,6 +94,13 @@ public interface Column { */ public boolean isDynamic(); + /** + * Returns true if this column is dynamic and has a TimeMap type. + * + * @return true if dynamic attribute type, false otherwise + */ + public boolean isDynamicAttribute(); + /** * Returns true if this column has a number type. * @@ -103,11 +108,17 @@ public interface Column { */ public boolean isNumber(); + /** + * Returns true if this column exists and belong to a table. + * + * @return true if exists, false otherwise + */ + public boolean exists(); + /** * Returns true if this column is a property. *

- * This is equivalent to test if the column's origin is - * Origin.PROPERTY + * This is equivalent to test if the column's origin is Origin.PROPERTY * * @return true if property, false otherwise */ @@ -139,6 +150,7 @@ public interface Column { * * @param withDiff true if column observer should provide column differences * @return the column observer + * @throws UnsupportedOperationException if observers are disabled (from Configuration) */ public ColumnObserver createColumnObserver(boolean withDiff); } diff --git a/store/src/main/java/org/gephi/graph/api/ColumnDiff.java b/src/main/java/org/gephi/graph/api/ColumnDiff.java similarity index 93% rename from store/src/main/java/org/gephi/graph/api/ColumnDiff.java rename to src/main/java/org/gephi/graph/api/ColumnDiff.java index deaf8703..5252168c 100644 --- a/store/src/main/java/org/gephi/graph/api/ColumnDiff.java +++ b/src/main/java/org/gephi/graph/api/ColumnDiff.java @@ -18,8 +18,8 @@ /** * Interface to retrieve elements touched in a column. *

- * This interface is associated with a {@link ColumnObserver} and provides an - * easy access to the elements which value has been modified. + * This interface is associated with a {@link ColumnObserver} and provides an easy access to the elements which value + * has been modified. */ public interface ColumnDiff { diff --git a/src/main/java/org/gephi/graph/api/ColumnIndex.java b/src/main/java/org/gephi/graph/api/ColumnIndex.java new file mode 100644 index 00000000..0508d18f --- /dev/null +++ b/src/main/java/org/gephi/graph/api/ColumnIndex.java @@ -0,0 +1,107 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.api; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * A column index is associated with a column and keeps track of each unique value and can also return the minimum and + * maximum values in case of a sortable value type. + * + * @param value type + * @param Element class + */ +public interface ColumnIndex extends Iterable>> { + + /** + * Counts the elements with value. + * + * @param value the value + * @return the number of elements in the column index with value, or zero if none + */ + int count(K value); + + /** + * Gets an Iterable of all elements in the column index with value. + * + * @param value the value + * @return an iterable with element with value + */ + Iterable get(K value); + + /** + * Returns all unique values. + * + * @return a collection of all unique values + */ + Collection values(); + + /** + * Counts the unique values. + * + * @return the number of distinct values. + */ + int countValues(); + + /** + * Counts the elements. + * + * @return the number of elements in column + */ + int countElements(); + + /** + * Returns whether the column index is numeric and sortable, and therefore methods {@link #getMinValue()} and + * {@link #getMaxValue()} are available. + * + * @return true if sortable, false otherwise + */ + boolean isSortable(); + + /** + * Returns the minimum value. + *

+ * Only applies for sortable indices. + * + * @return the minimum value + */ + Number getMinValue(); + + /** + * Returns the maximum value. + *

+ * Only applies for sortable indices. + * + * @return the maximum value + */ + Number getMaxValue(); + + /** + * Returns the column for which this column index belongs to. + * + * @return the column + */ + Column getColumn(); + + /** + * Returns the index's version. The version is incremented every time the index is modified. + * + * @return index's version + */ + int getVersion(); +} diff --git a/store/src/main/java/org/gephi/graph/api/ColumnIterable.java b/src/main/java/org/gephi/graph/api/ColumnIterable.java similarity index 100% rename from store/src/main/java/org/gephi/graph/api/ColumnIterable.java rename to src/main/java/org/gephi/graph/api/ColumnIterable.java diff --git a/store/src/main/java/org/gephi/graph/api/ColumnObserver.java b/src/main/java/org/gephi/graph/api/ColumnObserver.java similarity index 74% rename from store/src/main/java/org/gephi/graph/api/ColumnObserver.java rename to src/main/java/org/gephi/graph/api/ColumnObserver.java index 18137585..89d8368a 100644 --- a/store/src/main/java/org/gephi/graph/api/ColumnObserver.java +++ b/src/main/java/org/gephi/graph/api/ColumnObserver.java @@ -18,20 +18,18 @@ /** * Observer over a column to monitor changes in the attributes values. *

- * Column observer can be used to periodically monitor changes made to a column. - * This scenario is common is multi-threaded applications where a thread is - * responsible to take action when something has changed in the column's data. + * Column observer can be used to periodically monitor changes made to a column. This scenario is common is + * multi-threaded applications where a thread is responsible to take action when something has changed in the column's + * data. *

- * Column observer users should periodically call the - * hasColumnChanged() method to check the status. Each call resets - * the observer so if the method returns true and the table doesn't change after - * that it will return false next time. + * Column observer users should periodically call the hasColumnChanged() method to check the status. Each + * call resets the observer so if the method returns true and the table doesn't change after that it will return false + * next time. *

- * This observer monitors all the rows for this column and consider something - * has changed when an element's value for this column has been changed. + * This observer monitors all the rows for this column and consider something has changed when an element's value for + * this column has been changed. *

- * Observers should be destroyed when not needed anymore. A new observer can be - * obtained from the Column. + * Observers should be destroyed when not needed anymore. A new observer can be obtained from the Column. * * @see Column */ diff --git a/src/main/java/org/gephi/graph/api/Configuration.java b/src/main/java/org/gephi/graph/api/Configuration.java new file mode 100644 index 00000000..150ee2fd --- /dev/null +++ b/src/main/java/org/gephi/graph/api/Configuration.java @@ -0,0 +1,673 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.api; + +import org.gephi.graph.api.types.IntervalDoubleMap; +import org.gephi.graph.api.types.TimestampDoubleMap; +import org.gephi.graph.impl.ConfigurationImpl; + +/** + * Global configuration set at initialization. + *

+ * This class can be passed as a parameter to {@link GraphModel.Factory#newInstance(org.gephi.graph.api.Configuration)} + * to create a GraphModel with custom configuration. + *

+ * Create instances by using the builder: + * + *

+ * Configuration config = Configuration.builder().build();
+ * 
+ *

+ * Note that setting configurations after the GraphModel has been created won't have any effect. + *

+ * By default, both node and edge id types are String.class and the time representation is + * TIMESTAMP. + *

+ * See the builder documentation for more information on default values. + * + * @see GraphModel + * @see Builder + */ +public class Configuration { + + private ConfigurationImpl delegate; + + /** + * Default constructor. + * + * @deprecated Use the builder() method instead. + */ + @Deprecated + public Configuration() { + this.delegate = new ConfigurationImpl(); + } + + protected Configuration(ConfigurationImpl delegate) { + this.delegate = delegate; + } + + /** + * Creates a new builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Configuration builder. + *

+ * + * Note that this class is not thread-safe. + */ + public static class Builder { + + private ConfigurationImpl configuration; + + private Builder() { + configuration = new ConfigurationImpl(); + } + + private Builder(ConfigurationImpl configuration) { + this.configuration = configuration; + } + + /** + * Builds the configuration. + * + * @return the configuration + */ + public Configuration build() { + // Check for potential inconsistencies + if (!configuration.isEnableNodeProperties() && configuration.isEnableSpatialIndex()) { + throw new IllegalStateException("Spatial index can't be enabled if node properties are disabled"); + } + + return new Configuration(configuration); + } + + /** + * Sets the node id type. + *

+ * Only simple types such as primitives, wrappers and String are supported. + *

+ * Default is String.class. + * + * @param nodeIdType node id type + * @return this builder + * @throws IllegalArgumentException if the type isn't supported + */ + public Builder nodeIdType(final Class nodeIdType) { + checkSimpleType(nodeIdType); + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public Class getNodeIdType() { + return nodeIdType; + } + }); + return this; + } + + /** + * Sets the edge id type. + *

+ * Only simple types such as primitives, wrappers and String are supported. + *

+ * Default is String.class. + * + * @param edgeIdType edge id type + * @return this builder + * @throws IllegalArgumentException if the type isn't supported + */ + public Builder edgeIdType(final Class edgeIdType) { + checkSimpleType(edgeIdType); + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public Class getEdgeIdType() { + return edgeIdType; + } + }); + return this; + } + + /** + * Sets the edge label type. + *

+ * Only simple types such as primitives, wrappers and String are supported. + *

+ * Default is String.class. + * + * @param edgeLabelType edge label type + * @return this builder + * @throws IllegalArgumentException if the type isn't supported + */ + public Builder edgeLabelType(final Class edgeLabelType) { + checkSimpleType(edgeLabelType); + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public Class getEdgeLabelType() { + return edgeLabelType; + } + }); + return this; + } + + /** + * Sets the edge weight type. + *

+ * Double, IntervalDoubleMap and TimestampDoubleMap are supported. + *

+ * Default is Double.class. + * + * @param edgeWeightType edge weight type + * @return this builder + * @throws IllegalArgumentException if the type isn't supported + */ + public Builder edgeWeightType(final Class edgeWeightType) { + if (!(Double.class.equals(edgeWeightType) || TimestampDoubleMap.class + .equals(edgeWeightType) || IntervalDoubleMap.class.equals(edgeWeightType))) { + throw new IllegalArgumentException("Unsupported type " + edgeWeightType + .getCanonicalName() + ", should be Double, IntervalDoubleMap or TimestampDoubleMap"); + } + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public Class getEdgeWeightType() { + return edgeWeightType; + } + }); + return this; + } + + /** + * Sets the time representation. + *

+ * Default is TIMESTAMP. + * + * @param timeRepresentation time representation + * @return this builder + */ + public Builder timeRepresentation(final TimeRepresentation timeRepresentation) { + if (timeRepresentation == null) { + throw new IllegalArgumentException("timeRepresentation cannot be null"); + } + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public TimeRepresentation getTimeRepresentation() { + return timeRepresentation; + } + }); + return this; + } + + /** + * Sets whether to create an edge weight column. + *

+ * Default is true. + * + * @param edgeWeightColumn edge weight column + * @return this builder + */ + public Builder edgeWeightColumn(final boolean edgeWeightColumn) { + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public Boolean getEdgeWeightColumn() { + return edgeWeightColumn; + } + }); + return this; + } + + /** + * Sets whether to enable observers on tables and columns. + *

+ * Default is true. + * + * @param enableObservers enable observers + * @return this builder + */ + public Builder enableObservers(final boolean enableObservers) { + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public boolean isEnableObservers() { + return enableObservers; + } + }); + return this; + } + + /** + * Sets whether to enable auto edge type registration. + *

+ * If enabled, edge types are automatically registered when edges are added. If disabled, one needs to call + * {@link GraphModel#addEdgeType(Object)} explicitly for each type. + *

+ * Default is true. + * + * @param enableAutoEdgeTypeRegistration enable auto edge type registration + * @return this builder + */ + public Builder enableAutoEdgeTypeRegistration(final boolean enableAutoEdgeTypeRegistration) { + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public boolean isEnableAutoEdgeTypeRegistration() { + return enableAutoEdgeTypeRegistration; + } + }); + return this; + } + + /** + * Sets whether to enable node properties. + *

+ * If enabled, {@link NodeProperties} are created for each node. If those properties aren't needed, disabling + * them can save memory. + *

+ * Default is true. + * + * @param enableNodeProperties enable node properties + * @return this builder + */ + public Builder enableNodeProperties(final boolean enableNodeProperties) { + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public boolean isEnableNodeProperties() { + return enableNodeProperties; + } + }); + return this; + } + + /** + * Sets whether to enable edge properties. + *

+ * If enabled, {@link EdgeProperties} are created for each edge. If those properties aren't needed, disabling + * them can save memory. + *

+ * Default is true. + * + * @param enableEdgeProperties enable edge properties + * @return this builder + */ + public Builder enableEdgeProperties(final boolean enableEdgeProperties) { + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public boolean isEnableEdgeProperties() { + return enableEdgeProperties; + } + }); + return this; + } + + /** + * Sets whether to enable the {@link SpatialIndex}. + *

+ * If enabled, the spatial index is updated while node positions are updated. If unused, disabling it is + * recommended as it adds some overhead. + *

+ * The spatial index can be retrieved from {@link Graph#getSpatialIndex()}. + *

+ * Default is false. + * + * @param enableSpatialIndex enable edge properties + * @return this builder + */ + public Builder enableSpatialIndex(final boolean enableSpatialIndex) { + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public boolean isEnableSpatialIndex() { + return enableSpatialIndex; + } + }); + return this; + } + + /** + * Sets whether to enable the reverse indexing of node attributes. + *

+ * If enabled, the reverse index is updated while node attributes are updated. This powers + * {@link GraphModel#getNodeIndex()} but has a negative impact on memory usage (as any reverse index does). When + * disabled, the features of {@link Index} are still available but need to iterate over all nodes to + * return results. + *

+ * Default is true. + * + * @param enableIndexNodes enable node attribute indexing + * @return this builder + */ + public Builder enableIndexNodes(final boolean enableIndexNodes) { + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public boolean isEnableIndexNodes() { + return enableIndexNodes; + } + }); + return this; + } + + /** + * Sets whether to enable the reverse indexing of edge attributes. + *

+ * If enabled, the reverse index is updated while node attributes are updated. This powers + * {@link GraphModel#getEdgeIndex()} but has a negative impact on memory usage (as any reverse index does). When + * disabled, the features of {@link Index} are still available but need to iterate over all nodes to + * return results. + *

+ * Default is true. + * + * @param enableIndexEdges enable edge attribute indexing + * @return this builder + */ + public Builder enableIndexEdges(final boolean enableIndexEdges) { + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public boolean isEnableIndexEdges() { + return enableIndexEdges; + } + }); + return this; + } + + /** + * Sets whether to enable the reverse indexing of timestamps and intervals. + *

+ * If enabled, the reverse index is updated while element's time set is updated. This powers + * {@link GraphModel#getNodeTimeIndex()} and {@link GraphModel#getEdgeTimeIndex()} ()} but has a negative impact + * on memory usage (as any reverse index does). + *

+ * Default is true. + * + * @param enableIndexTime enable time indexing + * @return this builder + */ + public Builder enableIndexTime(final boolean enableIndexTime) { + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public boolean isEnableIndexTime() { + return enableIndexTime; + } + }); + return this; + } + + /** + * Sets whether to enable multiple edges of the same type between two nodes. + *

+ * If disabled, only a single edge of a given type can exist between two nodes. + *

+ * Default is false. + * + * @param enableParallelEdgesSameType enable parallel edges of the same type + * @return this builder + */ + public Builder enableParallelEdgesSameType(final boolean enableParallelEdgesSameType) { + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public boolean isEnableParallelEdgesSameType() { + return enableParallelEdgesSameType; + } + }); + return this; + } + + /** + * Sets whether to enable auto locking when using read/write APIs. + *

+ * If disabled, the client is responsible for handling multithreading themselves or calling methods such as + * {@link Graph#readLock()} or {@link Graph#writeLock()}. If enabled, each read methods (including iterators) + * handle locking. Similarly, each write method handle locking. + *

+ * Default is true. + * + * @param enableAutoLocking enable auto locking for read/write operations + * @see GraphLock + * @return this builder + */ + public Builder enableAutoLocking(final boolean enableAutoLocking) { + this.configuration = new ConfigurationImpl(new Configuration(this.configuration) { + @Override + public boolean isEnableAutoLocking() { + return enableAutoLocking; + } + }); + return this; + } + + private static void checkSimpleType(Class type) { + if (!AttributeUtils.isSimpleType(type)) { + throw new IllegalArgumentException("Unsupported type " + type.getCanonicalName()); + } + } + } + + /** + * Returns the node id type. + * + * @return node id type + */ + public Class getNodeIdType() { + return delegate.getNodeIdType(); + } + + /** + * Sets the node id type. + *

+ * Only simple types such as primitives, wrappers and String are supported. + * + * @deprecated Use {@link #builder()} instead. + * + * @param nodeIdType node id type + * @throws IllegalArgumentException if the type isn't supported + */ + @Deprecated + public void setNodeIdType(Class nodeIdType) { + this.delegate = new Builder(this.delegate).nodeIdType(nodeIdType).configuration; + } + + /** + * Returns the edge id type. + * + * @return edge id type + */ + public Class getEdgeIdType() { + return delegate.getEdgeIdType(); + } + + /** + * Sets the edge id type. + *

+ * Only simple types such as primitives, wrappers and String are supported. + * + * @deprecated Use {@link #builder()} instead. + * + * @param edgeIdType edge id type + * @throws IllegalArgumentException if the type isn't supported + */ + @Deprecated + public void setEdgeIdType(Class edgeIdType) { + this.delegate = new Builder(this.delegate).edgeIdType(edgeIdType).configuration; + } + + /** + * Returns the edge label type. + * + * @return edge label type + */ + public Class getEdgeLabelType() { + return delegate.getEdgeLabelType(); + } + + /** + * Sets the edge label type. + * + * @deprecated Use {@link #builder()} instead. + * + * @param edgeLabelType edge label type + * @throws IllegalArgumentException if the type isn't supported + */ + @Deprecated + public void setEdgeLabelType(Class edgeLabelType) { + this.delegate = new Builder(this.delegate).edgeLabelType(edgeLabelType).configuration; + } + + /** + * Returns the edge weight type. + * + * @return edge weight type + */ + public Class getEdgeWeightType() { + return delegate.getEdgeWeightType(); + } + + /** + * Sets the edge weight type. + * + * @deprecated Use {@link #builder()} instead. + * + * @param edgeWeightType edge weight type + * @throws IllegalArgumentException if the type isn't supported + */ + @Deprecated + public void setEdgeWeightType(Class edgeWeightType) { + this.delegate = new Builder(this.delegate).edgeWeightType(edgeWeightType).configuration; + } + + /** + * Returns the time representation. + * + * @return time representation + */ + public TimeRepresentation getTimeRepresentation() { + return delegate.getTimeRepresentation(); + } + + /** + * Sets the time representation. + * + * @deprecated Use {@link #builder()} instead. + * + * @param timeRepresentation time representation + */ + @Deprecated + public void setTimeRepresentation(TimeRepresentation timeRepresentation) { + this.delegate = new Builder(this.delegate).timeRepresentation(timeRepresentation).configuration; + } + + /** + * Returns whether an edge weight column is created. + * + * @return edge weight column + */ + public Boolean getEdgeWeightColumn() { + return delegate.isEdgeWeightColumn(); + } + + /** + * Sets whether to create an edge weight column. + *

+ * + * @deprecated Use {@link #builder()} instead. + * + * @param edgeWeightColumn edge weight column + */ + @Deprecated + public void setEdgeWeightColumn(Boolean edgeWeightColumn) { + this.delegate = new Builder(this.delegate).edgeWeightColumn(edgeWeightColumn).configuration; + } + + public boolean isEnableAutoLocking() { + return delegate.isEnableAutoLocking(); + } + + public boolean isEnableAutoEdgeTypeRegistration() { + return delegate.isEnableAutoEdgeTypeRegistration(); + } + + public boolean isEnableIndexNodes() { + return delegate.isEnableIndexNodes(); + } + + public boolean isEnableIndexEdges() { + return delegate.isEnableIndexEdges(); + } + + public boolean isEnableIndexTime() { + return delegate.isEnableIndexTime(); + } + + public boolean isEnableObservers() { + return delegate.isEnableObservers(); + } + + public boolean isEnableNodeProperties() { + return delegate.isEnableNodeProperties(); + } + + public boolean isEnableEdgeProperties() { + return delegate.isEnableEdgeProperties(); + } + + public boolean isEnableSpatialIndex() { + return delegate.isEnableSpatialIndex(); + } + + public boolean isEnableParallelEdgesSameType() { + return delegate.isEnableParallelEdgesSameType(); + } + + /** + * Copy this configuration. + * + * @return a copy of this configuration + */ + public Configuration copy() { + return new Configuration(delegate); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Configuration)) { + return false; + } + + Configuration that = (Configuration) o; + + return delegate.equals(that.delegate); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + /** + * Returns a string representation of the differences between this configuration and another one. + * + * @param other the other configuration + * @return a string representation of the differences + */ + public String diffAsString(Configuration other) { + return delegate.diffAsString(other.delegate); + } +} diff --git a/store/src/main/java/org/gephi/graph/api/DirectedGraph.java b/src/main/java/org/gephi/graph/api/DirectedGraph.java similarity index 96% rename from store/src/main/java/org/gephi/graph/api/DirectedGraph.java rename to src/main/java/org/gephi/graph/api/DirectedGraph.java index b34f80c1..08c232b2 100644 --- a/store/src/main/java/org/gephi/graph/api/DirectedGraph.java +++ b/src/main/java/org/gephi/graph/api/DirectedGraph.java @@ -18,8 +18,8 @@ /** * Directed graph. *

- * This interface has additional methods specific to directed graphs compared to - * the Graph interface it inherits from. + * This interface has additional methods specific to directed graphs compared to the Graph interface it + * inherits from. */ public interface DirectedGraph extends Graph { @@ -34,8 +34,7 @@ public interface DirectedGraph extends Graph { public Edge getEdge(Node source, Node target); /** - * Gets the edge adjacent to source and target with an edge of the given - * type. + * Gets the edge adjacent to source and target with an edge of the given type. * * @param source the source node * @param target the target node @@ -56,8 +55,7 @@ public interface DirectedGraph extends Graph { public boolean isAdjacent(Node source, Node target); /** - * Returns true if source and target are adjacent with an edge of the given - * type. + * Returns true if source and target are adjacent with an edge of the given type. * * @param source the source node * @param target the target node @@ -146,8 +144,7 @@ public interface DirectedGraph extends Graph { /** * Gets the edge in the other direction of the given edge. *

- * This takes in account the edge type so only edges of the same type can be - * mutual. + * This takes in account the edge type so only edges of the same type can be mutual. * * @param edge the edge to get the mutual edge * @return the mutual edge, or null diff --git a/store/src/main/java/org/gephi/graph/api/DirectedSubgraph.java b/src/main/java/org/gephi/graph/api/DirectedSubgraph.java similarity index 100% rename from store/src/main/java/org/gephi/graph/api/DirectedSubgraph.java rename to src/main/java/org/gephi/graph/api/DirectedSubgraph.java diff --git a/store/src/main/java/org/gephi/graph/api/Edge.java b/src/main/java/org/gephi/graph/api/Edge.java similarity index 90% rename from store/src/main/java/org/gephi/graph/api/Edge.java rename to src/main/java/org/gephi/graph/api/Edge.java index e0d596ff..022678e4 100644 --- a/store/src/main/java/org/gephi/graph/api/Edge.java +++ b/src/main/java/org/gephi/graph/api/Edge.java @@ -62,8 +62,7 @@ public interface Edge extends Element, EdgeProperties { /** * Returns the edge's weight in the given graph view. *

- * Views can configure a time interval and therefore the edge weight over - * time may vary. + * Views can configure a time interval and therefore the edge weight over time may vary. * * @param view graph view * @return weight @@ -114,6 +113,13 @@ public interface Edge extends Element, EdgeProperties { */ public int getType(); + /** + * Sets the edge's type. + * + * @param type the type + */ + public void setType(int type); + /** * Returns the edge's type label. * @@ -134,4 +140,11 @@ public interface Edge extends Element, EdgeProperties { * @return true if directed, false otherwise */ public boolean isDirected(); + + /** + * Returns true if this edge is directed and another edge exists in the opposite direction. + * + * @return true if mutual, false otherwise + */ + public boolean isMutual(); } diff --git a/store/src/main/java/org/gephi/graph/api/EdgeIterable.java b/src/main/java/org/gephi/graph/api/EdgeIterable.java similarity index 69% rename from store/src/main/java/org/gephi/graph/api/EdgeIterable.java rename to src/main/java/org/gephi/graph/api/EdgeIterable.java index 69a243e7..79c7b254 100644 --- a/store/src/main/java/org/gephi/graph/api/EdgeIterable.java +++ b/src/main/java/org/gephi/graph/api/EdgeIterable.java @@ -19,6 +19,9 @@ import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; /** * An edge iterable. @@ -54,10 +57,31 @@ public interface EdgeIterable extends ElementIterable { @Override public Collection toCollection(); + /** + * Returns the iterator content as a set. + * + * @return edge set + */ + @Override + public Set toSet(); + + /** + * Returns a Spliterator over the edges. + *

+ * Implementations return a splittable, sized, fail-fast spliterator suitable for parallel streams. When not + * possible, a non-splittable spliterator is returned. + * + * @return edge spliterator + */ + @Override + default Spliterator spliterator() { + return ElementIterable.super.spliterator(); + } + /** * Empty edge iterable. */ - static final class EdgeIterableEmpty implements Iterator, EdgeIterable { + final class EdgeIterableEmpty implements Iterator, EdgeIterable { @Override public boolean hasNext() { @@ -79,6 +103,11 @@ public Iterator iterator() { return this; } + @Override + public Spliterator spliterator() { + return Spliterators.emptySpliterator(); + } + @Override public Edge[] toArray() { return new Edge[0]; @@ -89,6 +118,11 @@ public Collection toCollection() { return Collections.EMPTY_LIST; } + @Override + public Set toSet() { + return Collections.EMPTY_SET; + } + @Override public void doBreak() { } diff --git a/store/src/main/java/org/gephi/graph/api/EdgeProperties.java b/src/main/java/org/gephi/graph/api/EdgeProperties.java similarity index 100% rename from store/src/main/java/org/gephi/graph/api/EdgeProperties.java rename to src/main/java/org/gephi/graph/api/EdgeProperties.java diff --git a/store/src/main/java/org/gephi/graph/api/Element.java b/src/main/java/org/gephi/graph/api/Element.java similarity index 89% rename from store/src/main/java/org/gephi/graph/api/Element.java rename to src/main/java/org/gephi/graph/api/Element.java index 6a347997..45746085 100644 --- a/store/src/main/java/org/gephi/graph/api/Element.java +++ b/src/main/java/org/gephi/graph/api/Element.java @@ -39,6 +39,11 @@ public interface Element extends ElementProperties { /** * Gets the attribute for the given key. + *

+ * For dynamic columns the returned {@link org.gephi.graph.api.types.TimeMap TimeMap} or + * {@link org.gephi.graph.api.types.TimeSet TimeSet} is the instance held by this element. Mutating it directly + * leaves the time index stale: use the setAttribute and removeAttribute methods that take + * a time instead. * * @param key column's key * @return attribute value, or null @@ -47,6 +52,11 @@ public interface Element extends ElementProperties { /** * Gets the attribute for the given column. + *

+ * For dynamic columns the returned {@link org.gephi.graph.api.types.TimeMap TimeMap} or + * {@link org.gephi.graph.api.types.TimeSet TimeSet} is the instance held by this element. Mutating it directly + * leaves the time index stale: use the setAttribute and removeAttribute methods that take + * a time instead. * * @param column column * @return attribute value, or null @@ -108,8 +118,7 @@ public interface Element extends ElementProperties { public Object getAttribute(Column column, GraphView view); /** - * Returns an iterable over all the keys and values over time for the given - * column. + * Returns an iterable over all the keys and values over time for the given (dynamic) column. * * @param column column * @return time attribute iterable @@ -319,6 +328,15 @@ public interface Element extends ElementProperties { */ public Interval[] getIntervals(); + /** + * Gets the time bounds. + *

+ * The time bounds is an interval made of the minimum and maximum time observed in this element. + * + * @return time bounds + */ + public Interval getTimeBounds(); + /** * Clears all attribute values. */ diff --git a/store/src/main/java/org/gephi/graph/api/ElementIterable.java b/src/main/java/org/gephi/graph/api/ElementIterable.java similarity index 71% rename from store/src/main/java/org/gephi/graph/api/ElementIterable.java rename to src/main/java/org/gephi/graph/api/ElementIterable.java index 09eb3eb6..c22417b6 100644 --- a/store/src/main/java/org/gephi/graph/api/ElementIterable.java +++ b/src/main/java/org/gephi/graph/api/ElementIterable.java @@ -19,6 +19,11 @@ import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; /** * Element iterable. @@ -40,6 +45,24 @@ public interface ElementIterable extends Iterable { @Override public Iterator iterator(); + /** + * Creates a new sequential stream, based on the spliterator returned. + * + * @return stream + */ + default Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + /** + * Creates a new sequential and parallel stream, based on the spliterator returned. + * + * @return stream + */ + default Stream parallelStream() { + return StreamSupport.stream(spliterator(), true); + } + /** * Returns the iterator content as an array. * @@ -54,6 +77,13 @@ public interface ElementIterable extends Iterable { */ public Collection toCollection(); + /** + * Returns the iterator content as a set. + * + * @return element set + */ + public Set toSet(); + /** * Break the iterator and release read lock (if any). */ @@ -84,6 +114,11 @@ public Iterator iterator() { return this; } + @Override + public Spliterator spliterator() { + return Spliterators.emptySpliterator(); + } + @Override public Element[] toArray() { return new Node[0]; @@ -94,6 +129,11 @@ public Collection toCollection() { return Collections.EMPTY_LIST; } + @Override + public Set toSet() { + return Collections.EMPTY_SET; + } + @Override public void doBreak() { } diff --git a/store/src/main/java/org/gephi/graph/api/ElementProperties.java b/src/main/java/org/gephi/graph/api/ElementProperties.java similarity index 100% rename from store/src/main/java/org/gephi/graph/api/ElementProperties.java rename to src/main/java/org/gephi/graph/api/ElementProperties.java diff --git a/store/src/main/java/org/gephi/graph/api/Estimator.java b/src/main/java/org/gephi/graph/api/Estimator.java similarity index 92% rename from store/src/main/java/org/gephi/graph/api/Estimator.java rename to src/main/java/org/gephi/graph/api/Estimator.java index 9eb8cc6c..39c23397 100644 --- a/store/src/main/java/org/gephi/graph/api/Estimator.java +++ b/src/main/java/org/gephi/graph/api/Estimator.java @@ -18,8 +18,8 @@ /** * Estimators specify the strategy to merge attribute values over time. *

- * Estimators are associated with actions that require to transform a sorted set - * of values over time into a single value. + * Estimators are associated with actions that require to transform a sorted set of values over time into a single + * value. */ public enum Estimator { @@ -59,8 +59,7 @@ public boolean is(Estimator estimator) { } /** - * Returns true if this estimator is any of the given - * estimators. + * Returns true if this estimator is any of the given estimators. * * @param estimators estimators to test equality * @return true if estimators contains this estimator diff --git a/store/src/main/java/org/gephi/graph/api/Graph.java b/src/main/java/org/gephi/graph/api/Graph.java similarity index 87% rename from store/src/main/java/org/gephi/graph/api/Graph.java rename to src/main/java/org/gephi/graph/api/Graph.java index 5209c1dc..217d0d49 100644 --- a/store/src/main/java/org/gephi/graph/api/Graph.java +++ b/src/main/java/org/gephi/graph/api/Graph.java @@ -89,6 +89,22 @@ public interface Graph { */ public boolean removeAllNodes(Collection nodes); + /** + * Retains only nodes in this graph that are contained in the specified collection. + * + * @param nodes the node collection + * @return true if at least one node has been removed, false otherwise + */ + public boolean retainNodes(Collection nodes); + + /** + * Retains only edges in this graph that are contained in the specified collection. + * + * @param edges the edge collection + * @return true if at least one edge has been removed, false otherwise + */ + public boolean retainEdges(Collection edges); + /** * Returns true if node is contained in this graph. * @@ -113,6 +129,14 @@ public interface Graph { */ public Node getNode(Object id); + /** + * Gets a node given its store id. + * + * @param storeId the store id + * @return the node, or null if not found + */ + public Node getNodeByStoreId(int storeId); + /** * Returns true if a node with id as identifier exists. * @@ -129,6 +153,14 @@ public interface Graph { */ public Edge getEdge(Object id); + /** + * Gets an edge given its store id. + * + * @param storeId the store id + * @return the edge, or null if not found + */ + public Edge getEdgeByStoreId(int storeId); + /** * Returns true if an edge with id as identifier exists. * @@ -197,6 +229,14 @@ public interface Graph { */ public EdgeIterable getEdges(); + /** + * Gets all the edges of a particular type in the graph. + * + * @param type edge type + * @return an edge iterable over all edges of this type + */ + public EdgeIterable getEdges(int type); + /** * Gets all the self-loop edges in the graph. * @@ -303,14 +343,12 @@ public interface Graph { public boolean isAdjacent(Node node1, Node node2); /** - * Returns true if node1 and node2 are adjacent with an edge of the given - * type. + * Returns true if node1 and node2 are adjacent with an edge of the given type. * * @param node1 the first node * @param node2 the second node * @param type the edge type - * @return true if node1 and node2 are adjacent with an edge og the given - * type, false otherwise + * @return true if node1 and node2 are adjacent with an edge og the given type, false otherwise */ public boolean isAdjacent(Node node1, Node node2, int type); @@ -453,6 +491,16 @@ public interface Graph { */ public GraphModel getModel(); + /** + * Returns a version number for this graph. + *

+ * The version gets altered when the graph structure changes. + * + * @see GraphObserver for a more sophisticated way to track changes + * @return graph version + */ + public int getVersion(); + /** * Returns true if this graph is directed. * @@ -499,5 +547,18 @@ public interface Graph { */ public void writeUnlock(); - public SpatialContext getSpatialContext(); + /** + * Returns the graph lock, in case locking is enabled. The graph lock controls the multi-thread access to the graph + * structure. + * + * @return graph lock + */ + GraphLock getLock(); + + /** + * Returns the spatial index. + * + * @return spatial index + */ + SpatialIndex getSpatialIndex(); } diff --git a/store/src/main/java/org/gephi/graph/api/GraphBridge.java b/src/main/java/org/gephi/graph/api/GraphBridge.java similarity index 75% rename from store/src/main/java/org/gephi/graph/api/GraphBridge.java rename to src/main/java/org/gephi/graph/api/GraphBridge.java index d2c1f898..8d413104 100644 --- a/store/src/main/java/org/gephi/graph/api/GraphBridge.java +++ b/src/main/java/org/gephi/graph/api/GraphBridge.java @@ -18,27 +18,23 @@ /** * Helper that helps transfer elements from another graph store. *

- * This bridge can be used to insert elements that belong to another graph in - * this graph store. It operates a deep copy so the destination elements are - * independent from the source and have exactly the same properties and - * attributes. + * This bridge can be used to insert elements that belong to another graph in this graph store. It operates a deep copy + * so the destination elements are independent from the source and have exactly the same properties and attributes. */ public interface GraphBridge { /** * Copy the given nodes to the current graph store. *

- * The nodes typically belong to another graph store. If nodes - * already exists in the current graph they will be ignored. + * The nodes typically belong to another graph store. If nodes already exists in the current graph they + * will be ignored. *

- * All edges attached to nodes will be copied as well if their - * source and target exists in this graph store. + * All edges attached to nodes will be copied as well if their source and target exists in this graph + * store. *

- * This operation takes care of copying attribute columns and values, edge - * type labels and element properties. + * This operation takes care of copying attribute columns and values, edge type labels and element properties. *

- * Beware that the source's configuration should match this graph store - * configuration. + * Beware that the source's configuration should match this graph store configuration. * * @param nodes nodes to copy */ diff --git a/store/src/main/java/org/gephi/graph/api/GraphDiff.java b/src/main/java/org/gephi/graph/api/GraphDiff.java similarity index 95% rename from store/src/main/java/org/gephi/graph/api/GraphDiff.java rename to src/main/java/org/gephi/graph/api/GraphDiff.java index a3c2c085..1c95ed51 100644 --- a/store/src/main/java/org/gephi/graph/api/GraphDiff.java +++ b/src/main/java/org/gephi/graph/api/GraphDiff.java @@ -18,8 +18,8 @@ /** * Interface to retrieve added and removed elements from the graph. *

- * This interface is associated with a {@link GraphObserver} and provides an - * easy access to the elements added or removed. + * This interface is associated with a {@link GraphObserver} and provides an easy access to the elements added or + * removed. */ public interface GraphDiff { diff --git a/store/src/main/java/org/gephi/graph/api/GraphFactory.java b/src/main/java/org/gephi/graph/api/GraphFactory.java similarity index 97% rename from store/src/main/java/org/gephi/graph/api/GraphFactory.java rename to src/main/java/org/gephi/graph/api/GraphFactory.java index 36d85cff..fdb7c302 100644 --- a/store/src/main/java/org/gephi/graph/api/GraphFactory.java +++ b/src/main/java/org/gephi/graph/api/GraphFactory.java @@ -20,8 +20,8 @@ *

* All new nodes and edges are created by this factory. *

- * Both nodes and edges have unique identifiers. If not provided, a unique id - * will be automatically assigned to the elements. + * Both nodes and edges have unique identifiers. If not provided, a unique id will be automatically assigned to the + * elements. */ public interface GraphFactory { diff --git a/src/main/java/org/gephi/graph/api/GraphLock.java b/src/main/java/org/gephi/graph/api/GraphLock.java new file mode 100644 index 00000000..5ae4d08c --- /dev/null +++ b/src/main/java/org/gephi/graph/api/GraphLock.java @@ -0,0 +1,153 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.api; + +import java.util.concurrent.TimeUnit; + +/** + * Wrapper around ReentrantReadWriteLock that controls multi-thread access to the graph structure. + */ +public interface GraphLock { + + /** + * Acquires the read lock. Acquires the read lock if the write lock is not held by another thread and returns + * immediately. + *

+ * This call waits without bound. A read hold blocks every writer, and once a writer is waiting, new readers wait + * behind it as well, so a read hold that is never released stalls all graph operations. Do not hold the read lock + * across a wait on another thread, and do not abandon an auto-locking iterator (see {@link NodeIterable} and + * {@link EdgeIterable}) before it is exhausted or {@code doBreak()} has been called. Use + * {@link #tryReadLock(long, TimeUnit)} when the caller cannot afford to wait indefinitely. + */ + void readLock(); + + /** + * Attempts to release this lock. If the number of readers is now zero then the lock is made available for write + * lock attempts. If the current thread does not hold this lock then IllegalMonitorStateException is thrown. + * + * @throws IllegalMonitorStateException if the current thread does not hold this lock + */ + void readUnlock(); + + /** + * Release this lock by releasing all current read locks. + */ + void readUnlockAll(); + + /** + * Acquires the write lock. Acquires the write lock if neither the read nor write lock are held by another thread + * and returns immediately, setting the write lock hold count to one. + * + * @throws IllegalMonitorStateException if the current thread holds a read lock already + * @see #tryWriteLock(long, TimeUnit) + */ + void writeLock(); + + /** + * Attempts to release this lock. If the current thread is the holder of this lock then the hold count is + * decremented. If the hold count is now zero then the lock is released. If the current thread is not the holder of + * this lock then IllegalMonitorStateException is thrown. + *

+ * throws @IllegalMonitorStateException if the current thread does not hold this lock + */ + void writeUnlock(); + + /** + * Queries the number of reentrant read holds on this lock by the current thread. A reader thread has a hold on a + * lock for each lock action that is not matched by an unlock action. + * + * @return the number of holds on the read lock by the current thread, or zero if the read lock is not held by the + * current thread + */ + int getReadHoldCount(); + + /** + * Queries the number of reentrant write holds on this lock by the current thread. A writer thread has a hold on a + * lock for each lock action that is not matched by an unlock action. + * + * @return the number of holds on the write lock by the current thread, or zero if the write lock is not held by the + * current thread + */ + int getWriteHoldCount(); + + /** + * Acquires the read lock if the write lock is not held by another thread within the given waiting time. + *

+ * Unlike {@link #readLock()}, the wait is bounded and interruptible. A caller that receives {@code false} has not + * acquired the lock and must not call {@link #readUnlock()}. + * + * @param timeout the time to wait for the read lock + * @param unit the time unit of the timeout argument + * @return true if the read lock was acquired + * @throws InterruptedException if the current thread is interrupted while waiting + * @throws UnsupportedOperationException if the implementation does not support timed acquisition + */ + default boolean tryReadLock(long timeout, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + /** + * Acquires the write lock if neither the read nor write lock are held by another thread within the given waiting + * time. + *

+ * Unlike {@link #writeLock()}, the wait is bounded and interruptible. A caller that receives {@code false} has not + * acquired the lock and must not call {@link #writeUnlock()}. + * + * @param timeout the time to wait for the write lock + * @param unit the time unit of the timeout argument + * @return true if the write lock was acquired + * @throws IllegalMonitorStateException if the current thread holds a read lock already + * @throws InterruptedException if the current thread is interrupted while waiting + * @throws UnsupportedOperationException if the implementation does not support timed acquisition + */ + default boolean tryWriteLock(long timeout, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + /** + * Queries the number of read holds on this lock across all threads. This differs from {@link #getReadHoldCount()}, + * which counts only the current thread. A non-zero value while no thread is expected to be reading points at a read + * hold that was never released. + * + * @return the total number of read holds, or zero if the read lock is not held + * @throws UnsupportedOperationException if the implementation does not expose this + */ + default int getReadLockCount() { + throw new UnsupportedOperationException(); + } + + /** + * Queries whether the write lock is held by any thread. + * + * @return true if any thread holds the write lock + * @throws UnsupportedOperationException if the implementation does not expose this + */ + default boolean isWriteLocked() { + throw new UnsupportedOperationException(); + } + + /** + * Returns an estimate of the number of threads waiting to acquire either the read or the write lock. The value is + * an estimate because the number of threads may change while this method traverses internal data structures. It is + * designed for monitoring, not for synchronization control. + * + * @return the estimated number of waiting threads + * @throws UnsupportedOperationException if the implementation does not expose this + */ + default int getQueueLength() { + throw new UnsupportedOperationException(); + } +} diff --git a/store/src/main/java/org/gephi/graph/api/GraphModel.java b/src/main/java/org/gephi/graph/api/GraphModel.java similarity index 63% rename from store/src/main/java/org/gephi/graph/api/GraphModel.java rename to src/main/java/org/gephi/graph/api/GraphModel.java index 493f064d..d89a1703 100644 --- a/store/src/main/java/org/gephi/graph/api/GraphModel.java +++ b/src/main/java/org/gephi/graph/api/GraphModel.java @@ -18,72 +18,66 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.time.ZoneId; +import java.util.function.Predicate; import org.gephi.graph.impl.GraphModelImpl; -import org.joda.time.DateTimeZone; /** * Graph API's entry point. *

- * GraphModel is the entry point for this API and provide methods - * to create, access and modify graphs. It supports the most common graph - * paradigms and a complete support for graphs over time as well. + * GraphModel is the entry point for this API and provide methods to create, access and modify graphs. It + * supports the most common graph paradigms and a complete support for graphs over time as well. *

    - *
  • Directed: Edges can have a direction. Graphs can be directed, undirected - * or mixed. + *
  • Directed: Edges can have a direction. Graphs can be directed, undirected or mixed. *
  • Weighted: Edges can have a weight. *
  • Self-loops: Nodes can have self-loops. *
  • Labelled edges: Edges can have a label. - *
  • Properties: Each element in the graph can have properties associated to - * it. + *
  • Properties: Each element in the graph can have properties associated to it. *
*

* New instances can be obtained via the embedded factory: - * + * *

  * GraphModel model = GraphModel.Factory.newInstance();
  * 
- * - * This API revolves around a set of simple concepts. A GraphModel - * encapsulate all elements and metadata associated with a graph structure. In - * other words its a single graph but it also contains configuration, indices, + * + * A Configuration object can be passed to the factory: + * + *
+ * Configuration configuration = Configuration.builder().build();
+ * GraphModel model = GraphModel.Factory.newInstance(configuration);
+ * 
+ * + * This API revolves around a set of simple concepts. A GraphModel encapsulate all elements and metadata + * associated with a graph structure. In other words it's a single graph, but it also contains configuration, indices, * views and other less important services such as observers. *

- * Then, GraphModel gives access to the Graph - * interface, which focuses only on the graph structure and provide methods to - * add, remove, get and iterate nodes and edges. + * Then, GraphModel gives access to the Graph interface, which focuses only on the graph + * structure and provide methods to add, remove, get and iterate nodes and edges. *

- * The Graph contains nodes and edges, which both implement the - * Element interface. This Element interface gives - * access to methods that manipulate the attributes associated to nodes and - * edges. + * The Graph contains nodes and edges, which both implement the Element interface. This + * Element interface gives access to methods that manipulate the attributes associated to nodes and edges. *

- * Any number of attributes can be associated to elements but are managed - * through the Table and Column interfaces. A - * GraphModel gives access by default to a node and edge table. A - * Table is simply a list of columns, which each has a unique - * identifier and a type (e.g. integer). Attribute values can only be associated - * with elements for existing columns. + * Any number of attributes can be associated to elements but are managed through the Table and + * Column interfaces. A GraphModel gives access by default to a node and edge table. A + * Table is simply a list of columns, which each has a unique identifier and a type (e.g. integer). + * Attribute values can only be associated with elements for existing columns. *

- * Attributes are automatically indexed and information such as the number of - * elements with a particular value can be obtained from the Index - * interface. + * Attributes are automatically indexed and information such as the number of elements with a particular value can be + * obtained from the Index interface. *

- * Finally, this API supports the concept of graph views. A view is a mask on - * the graph structure and represents a subgraph. The user controls the set of - * nodes and edges in the view by obtaining a Subgraph for a - * specific GraphView. Views can directly be created and destroyed - * from this model. + * Finally, this API supports the concept of graph views. A view is a mask on the graph structure and represents a + * subgraph. The user controls the set of nodes and edges in the view by obtaining a Subgraph for a + * specific GraphView. Views can directly be created and destroyed from this model. *

* Elements should be created through the {@link #factory() } method. *

- * For performance reasons, edge labels are internally represented as integers - * and the mapping between arbitrary labels is managed through the - * {@link #addEdgeType(java.lang.Object) - * } and - * {@link #getEdgeType(java.lang.Object) } methods. By default, edges have a - * null label, which is internally represented as zero. + * For performance reasons, edge labels are internally represented as integers and the mapping between arbitrary labels + * is managed through the {@link #addEdgeType(java.lang.Object) } and {@link #getEdgeType(java.lang.Object) } methods. + * By default, edges have a null label, which is internally represented as zero. * * @see Graph + * @see Configuration * @see Element * @see Table * @see Column @@ -102,7 +96,7 @@ public static class Factory { * * @return new instance */ - public static GraphModel newInstance() { + public static GraphModelImpl newInstance() { return new GraphModelImpl(); } @@ -112,7 +106,7 @@ public static GraphModel newInstance() { * @param config configuration * @return new instance */ - public static GraphModel newInstance(Configuration config) { + public static GraphModelImpl newInstance(Configuration config) { return new GraphModelImpl(config); } } @@ -139,10 +133,25 @@ public static GraphModel read(DataInput input) throws IOException { } /** - * Read the input and return the read graph model without - * an explicit version header in the input. To be used with old - * graphstore serialized data prior to version 0.4 (first, that added - * the version header). + * Read the input into the given graph model. The provided graph model should be empty and the + * configurations should match between the provided model and the one being read. + * + * @param input data input to read from + * @return the graphmodel passed as parameter + * @throws IOException if an io error occurs + */ + public static GraphModel read(DataInput input, GraphModel graphModel) throws IOException { + try { + org.gephi.graph.impl.Serialization s = new org.gephi.graph.impl.Serialization(); + return s.deserializeGraphModel(input, graphModel); + } catch (ClassNotFoundException e) { + throw new IOException(e); + } + } + + /** + * Read the input and return the read graph model without an explicit version header in the input. + * To be used with old graphstore serialized data prior to version 0.4 (first, that added the version header). * * @param input data input to read from * @param graphStoreVersion Forced version to use @@ -171,6 +180,99 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce } } + /** + * Default columns utility. + */ + public static interface DefaultColumns { + + /** + * Return node identifier column. + *

+ * This is a read-only column. + * + * @return node id column + */ + public Column nodeId(); + + /** + * Return edge identifier column. + *

+ * This is a read-only column. + * + * @return edge id column + */ + public Column edgeId(); + + /** + * Return node label column. + * + * @return node label column + */ + public Column nodeLabel(); + + /** + * Return edge label column. + * + * @return edge label column + */ + public Column edgeLabel(); + + /** + * Return edge weigth column. + * + * @return edge weight column + */ + public Column edgeWeight(); + + /** + * Return node time-set (timestamp or interval) column. + * + * @return node time-set column + */ + public Column nodeTimeSet(); + + /** + * Return edge time-set (timestamp or interval) column. + * + * @return edge time-set column + */ + public Column edgeTimeSet(); + + /** + * Return node degree column. + * + * @return node degree column + */ + public Column degree(); + + /** + * Return node in-degree column. + *

+ * Only for directed graphs. + * + * @return node in-degree column + */ + public Column inDegree(); + + /** + * Return node out-degree column. + *

+ * Only for directed graphs. + * + * @return node out-degree column + */ + public Column outDegree(); + + /** + * Return edge type column. + *

+ * Only for multi-graphs. + * + * @return node in-degree column + */ + public Column edgeType(); + } + /** * Returns the graph factory. * @@ -269,6 +371,15 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce */ public void setVisibleView(GraphView view); + /** + * Returns the default columns. + *

+ * Default columns are always available for each element. + * + * @return default columns + */ + public DefaultColumns defaultColumns(); + /** * Adds a new edge type and returns the integer identifier. *

@@ -316,6 +427,15 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce */ public Object[] getEdgeTypeLabels(); + /** + * Returns the edge type labels. + * + * @param includeEmpty true to include labels without edges + * + * @return edge type labels + */ + public Object[] getEdgeTypeLabels(boolean includeEmpty); + /** * Returns true if the graph is directed. * @@ -353,6 +473,11 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce /** * Creates a new graph view. + *

+ * By default, the view applies to both nodes and edges, so this is equivalent to + * {@link #createView(boolean, boolean) createView(true, true)}. + *

+ * New views are by default empty, i.e. no nodes and no edges are visible in the view. * * @return newly created graph view */ @@ -361,9 +486,22 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce /** * Creates a new graph view. *

- * The node and edge parameters allows to restrict the view filtering to - * only nodes or only edges. By default, the view applies to both nodes and - * edges. + * The node and edge filters allows to restrict the view filtering to only nodes or only edges. If node only, all + * edges connected to included nodes will be included too. If edge only, all nodes are included but only the edges + * matching the view are included. + * + * @param nodeFilter predicate to filter nodes, or null to include all nodes + * @param edgeFilter predicate to filter edges, or null to include all edges + * @return newly created graph view + */ + public GraphView createView(Predicate nodeFilter, Predicate edgeFilter); + + /** + * Creates a new graph view. + *

+ * The node and edge parameters allows to restrict the view filtering to only nodes or only edges. If node only, all + * edges connected to included nodes will be included too. If edge only, all nodes are included but only the edges + * matching the view are included. * * @param node true to enable node view, false otherwise * @param edge true to enable edge view, false otherwise @@ -382,9 +520,8 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce /** * Creates a new graph based on an existing view. *

- * The node and edge parameters allows to restrict the view filtering to - * only nodes or only edges. By default, the view applies to both nodes and - * edges. + * The node and edge parameters allows to restrict the view filtering to only nodes or only edges. By default, the + * view applies to both nodes and edges. * * @param view view to copy * @param node true to enable node view, false otherwise @@ -403,8 +540,7 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce /** * Sets the given time interval to the view. *

- * Each view can be configured with a time interval to filter a graph over - * time. + * Each view can be configured with a time interval to filter a graph over time. * * @param view the view to configure * @param interval the time interval @@ -412,22 +548,18 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce public void setTimeInterval(GraphView view, Interval interval); /** - * Returns the node table. Contains all the columns associated to - * node elements. + * Returns the node table. Contains all the columns associated to node elements. *

- * A GraphModel always has node and edge tables - * by default. + * A GraphModel always has node and edge tables by default. * * @return node table, contains node columns */ public Table getNodeTable(); /** - * Returns the edge table. Contains all the columns associated to - * edge elements. + * Returns the edge table. Contains all the columns associated to edge elements. *

- * A GraphModel always has node and edge tables - * by default. + * A GraphModel always has node and edge tables by default. * * @return edge table, contains edge columns */ @@ -463,6 +595,23 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce */ public Index getEdgeIndex(GraphView view); + /** + * Gets the node or edge index depending on the column provided. + * + * @param table the table to get the index for + * @return element index, either node or edge + */ + public Index getElementIndex(Table table); + + /** + * Gets the node or edge index for the given graph view. + * + * @param table the table to get the index for + * @param view the view to get the index from + * @return edge index + */ + public Index getElementIndex(Table table, GraphView view); + /** * Gets the node time index. * @@ -496,8 +645,7 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce /** * Gets the time bounds. *

- * The time bounds is an interval made of the minimum and maximum time - * observed in the entire graph. + * The time bounds is an interval made of the minimum and maximum time observed in the entire graph. * * @return time bounds */ @@ -506,8 +654,7 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce /** * Gets the time bounds for the visible graph. *

- * The time bounds is an interval made of the minimum and maximum time - * observed in the entire graph. + * The time bounds is an interval made of the minimum and maximum time observed in the entire graph. * * @return time bounds */ @@ -516,8 +663,7 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce /** * Gets the time bounds for the given graph view. *

- * The time bounds is an interval made of the minimum and maximum time - * observed in the entire graph. + * The time bounds is an interval made of the minimum and maximum time observed in the entire graph. * * @param view the graph view * @return time bounds @@ -528,8 +674,7 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce * Creates and returns a new graph observer. * * @param graph the graph to observe - * @param withGraphDiff true to include graph difference feature, false - * otherwise + * @param withGraphDiff true to include graph difference feature, false otherwise * @return newly created graph observer */ public GraphObserver createGraphObserver(Graph graph, boolean withGraphDiff); @@ -553,14 +698,14 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce * * @return time zone */ - public DateTimeZone getTimeZone(); + public ZoneId getTimeZone(); /** * Sets the time zone used to display time. * * @param timeZone time zone */ - public void setTimeZone(DateTimeZone timeZone); + public void setTimeZone(ZoneId timeZone); /** * Returns the current configuration. @@ -571,21 +716,22 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce /** * Sets a new configuration for this graph model. - *

- * Note that this method only works if the graph model is empty. + * + * @deprecated setting configuration after graph model creation is no longer supported. Best is to use the + * {@link Configuration#builder()} to create a new configuration and then use it at graph model creation + * from {@link GraphModel.Factory#newInstance(Configuration)}. * * @param configuration new configuration - * @throws IllegalStateException if the graph model isn't empty */ + @Deprecated public void setConfiguration(Configuration configuration); /** * Returns the maximum store id number nodes have in this model. *

- * Each node has a unique store identifier which can be retrieved from - * {@link Node#getStoreId() }. This maximum number can help design algorithms - * thar rely on storing nodes in a array. Note that not all consecutive ids - * may be assigned. + * Each node has a unique store identifier which can be retrieved from {@link Node#getStoreId() }. This maximum + * number can help design algorithms thar rely on storing nodes in a array. Note that not all consecutive ids may be + * assigned. * * @return maximum node store id */ @@ -594,10 +740,9 @@ public static void write(DataOutput output, GraphModel graphModel) throws IOExce /** * Returns the maximum store id number edges have in this model. *

- * Each edge has a unique store identifier which can be retrieved from - * {@link Edge#getStoreId() }. This maximum number can help design algorithms - * thar rely on storing edges in a array. Note that not all consecutive ids - * may be assigned. + * Each edge has a unique store identifier which can be retrieved from {@link Edge#getStoreId() }. This maximum + * number can help design algorithms thar rely on storing edges in a array. Note that not all consecutive ids may be + * assigned. * * @return maximum edge store id */ diff --git a/store/src/main/java/org/gephi/graph/api/GraphObserver.java b/src/main/java/org/gephi/graph/api/GraphObserver.java similarity index 69% rename from store/src/main/java/org/gephi/graph/api/GraphObserver.java rename to src/main/java/org/gephi/graph/api/GraphObserver.java index 221e961a..19d984e5 100644 --- a/store/src/main/java/org/gephi/graph/api/GraphObserver.java +++ b/src/main/java/org/gephi/graph/api/GraphObserver.java @@ -18,26 +18,22 @@ /** * Observer over a graph to monitor changes and obtain the list of differences. *

- * The graph observer is a mechanism used to monitor periodically changes made - * to the graph. This scenario is common in multi-threaded application where a - * thread is modifying the graph and one or multiple threads need to take action - * when updates are made. + * The graph observer is a mechanism used to monitor periodically changes made to the graph. This scenario is common in + * multi-threaded application where a thread is modifying the graph and one or multiple threads need to take action when + * updates are made. *

- * Graph observer users should periodically call the - * hasGraphChanged() method to check the status. Each call resets - * the observer so if the method returns true and the graph doesn't change after - * that it will return false next time. + * Graph observer users should periodically call the hasGraphChanged() method to check the status. Each + * call resets the observer so if the method returns true and the graph doesn't change after that it will return false + * next time. *

- * In addition of a boolean flag whether the graph has changed, an observer can - * collect data about the differences such as nodes added or removed. Users - * should call the getDiff() method after calling + * In addition of a boolean flag whether the graph has changed, an observer can collect data about the differences such + * as nodes added or removed. Users should call the getDiff() method after calling * hasGraphChanged() to obtain the diff. *

- * Observers should be destroyed when not needed anymore. A new observer can be - * obtained from the GraphModel. + * Observers should be destroyed when not needed anymore. A new observer can be obtained from the + * GraphModel. *

- * Note that observer instances are not thread-safe and should not be called - * from multiple threads simultaneously. + * Note that observer instances are not thread-safe and should not be called from multiple threads simultaneously. * * @see GraphModel */ @@ -77,8 +73,7 @@ public interface GraphObserver { public boolean isDestroyed(); /** - * Returns true if this observer has never got its - * hasGraphChanged() method called. + * Returns true if this observer has never got its hasGraphChanged() method called. * * @return true if new observer, false otherwise */ diff --git a/store/src/main/java/org/gephi/graph/api/GraphView.java b/src/main/java/org/gephi/graph/api/GraphView.java similarity index 71% rename from store/src/main/java/org/gephi/graph/api/GraphView.java rename to src/main/java/org/gephi/graph/api/GraphView.java index 94e233d0..f6f6e035 100644 --- a/store/src/main/java/org/gephi/graph/api/GraphView.java +++ b/src/main/java/org/gephi/graph/api/GraphView.java @@ -18,27 +18,20 @@ /** * View on the graph. *

- * Each graph can have views and use these views to obtain subgraphs. A view is - * a filter on the main graph structure where some nodes and/or edges are - * missing. + * Each graph can have views and use these views to obtain subgraphs. A view is a filter on the main graph structure + * where some nodes and/or edges are missing. *

- * The graph model has a main view, which is always 100% of nodes and edges. - * Users can then create views and modify them by enabling/disabling elements. - * Views can only have elements which are in the model. As a consequence, if a - * element is removed from the graph it's also removed from all the views. By - * default, the view is empty. + * The graph model has a main view, which is always 100% of nodes and edges. Users can then create views and modify them + * by enabling/disabling elements. Views can only have elements which are in the model. As a consequence, if a element + * is removed from the graph it's also removed from all the views. By default, the view is empty. *

- * The main benefits of views is the ability to obtain a Subgraph - * object from it. Users can call the - * {@link GraphModel#getGraph(org.gephi.graph.api.GraphView) } method and obtain - * a subgraph backed by the view. Update operations such as add or remove on - * this graph are in-fact modifying the view rather than the model. Indeed, - * adding a node to a view is enabling this node in the view. Similarly for - * removal. + * The main benefits of views is the ability to obtain a Subgraph object from it. Users can call the + * {@link GraphModel#getGraph(org.gephi.graph.api.GraphView) } method and obtain a subgraph backed by the view. Update + * operations such as add or remove on this graph are in-fact modifying the view rather than the model. Indeed, adding a + * node to a view is enabling this node in the view. Similarly for removal. *

- * Views can apply on nodes only, edges only or both. This is configured when - * the view is created. Nodes-only view let the system automatically control the - * set of edges. Enabling a node in the view will automatically enable all it's + * Views can apply on nodes only, edges only or both. This is configured when the view is created. Nodes-only view let + * the system automatically control the set of edges. Enabling a node in the view will automatically enable all it's * edges if the opposite nodes are also in the view. * * @see GraphModel diff --git a/store/src/main/java/org/gephi/graph/api/Index.java b/src/main/java/org/gephi/graph/api/Index.java similarity index 84% rename from store/src/main/java/org/gephi/graph/api/Index.java rename to src/main/java/org/gephi/graph/api/Index.java index d9d6e5ee..12ce559c 100644 --- a/store/src/main/java/org/gephi/graph/api/Index.java +++ b/src/main/java/org/gephi/graph/api/Index.java @@ -18,10 +18,10 @@ import java.util.Collection; /** - * An index is associated with each table and keeps track of each unique value - * in indexed columns. + * An index is associated with each table and keeps track of each unique value in columns. *

- * + * Each column is associated with a @{{@link ColumnIndex}}. + * * @param Element class */ public interface Index { @@ -31,19 +31,16 @@ public interface Index { * * @param column the column to count values * @param value the value - * @return the number of elements in the index with value in - * column, or zero if none + * @return the number of elements in the index with value in column, or zero if none */ public int count(Column column, Object value); /** - * Gets an Iterable of all elements in the index with value in the - * given column. + * Gets an Iterable of all elements in the index with value in the given column. * * @param column the column to get values * @param value the value - * @return an iterable with element with value in column, - * or null if value not found + * @return an iterable with element with value in column, or null if value not found */ public Iterable get(Column column, Object value); @@ -73,9 +70,8 @@ public interface Index { /** * Returns whether the column is numeric and sortable, and therefore methods - * {@link #getMinValue(org.gephi.graph.api.Column)} and - * {@link #getMaxValue(org.gephi.graph.api.Column)} are available for the - * column. + * {@link #getMinValue(org.gephi.graph.api.Column)} and {@link #getMaxValue(org.gephi.graph.api.Column)} are + * available for the column. * * @param column the column * @return true if the column is sortable, false otherwise @@ -115,4 +111,12 @@ public interface Index { * @return the index name */ public String getIndexName(); + + /** + * Returns the column index for the given column. + * + * @param column the column to get the index for + * @return the column index + */ + public ColumnIndex getColumnIndex(Column column); } diff --git a/store/src/main/java/org/gephi/graph/api/Interval.java b/src/main/java/org/gephi/graph/api/Interval.java similarity index 88% rename from store/src/main/java/org/gephi/graph/api/Interval.java rename to src/main/java/org/gephi/graph/api/Interval.java index b7bf950d..10b9c257 100644 --- a/store/src/main/java/org/gephi/graph/api/Interval.java +++ b/src/main/java/org/gephi/graph/api/Interval.java @@ -61,8 +61,8 @@ public Interval(double low, double high) { * Compares this interval with the specified interval for order. * *

- * Any two intervals i and i' satisfy the interval trichotomy; - * that is, exactly one of the following three properties holds: + * Any two intervals i and i' satisfy the interval trichotomy; that is, exactly one of the following + * three properties holds: *

    *
  1. i and i' overlap *
  2. i is to the left of i' @@ -70,16 +70,14 @@ public Interval(double low, double high) { *
* *

- * Note that if two intervals are equal ({@code i.low = i'.low} and - * {@code i.high = i'.high}), they overlap as well. But if they simply - * overlap (for instance {@code i.low < i'.low} and {@code i.high > + * Note that if two intervals are equal ({@code i.low = i'.low} and {@code i.high = i'.high}), they overlap as well. + * But if they simply overlap (for instance {@code i.low < i'.low} and {@code i.high > * i'.high}) they aren't equal. * * @param interval the interval to be compared * - * @return a negative integer, zero, or a positive integer as this interval - * is to the left of, overlaps with, or is to the right of the - * specified interval. + * @return a negative integer, zero, or a positive integer as this interval is to the left of, overlaps with, or is + * to the right of the specified interval. * * @throws NullPointerException if {@code interval} is null. */ @@ -101,9 +99,8 @@ public int compareTo(Interval interval) { * Compares this interval to the given timetamp. * * @param timestamp timestamp - * @return a negative integer, zero or a positive integer if this interval - * is to the left of, overlaps with, or is to the right with the - * specified timestamp. + * @return a negative integer, zero or a positive integer if this interval is to the left of, overlaps with, or is + * to the right with the specified timestamp. * * @throws NullPointerException if {@code timestamp} is null. */ @@ -142,14 +139,12 @@ public double getHigh() { * Compares this interval with the specified object for equality. * *

- * Note that two intervals are equal if {@code i.low = i'.low} and - * {@code i.high = i'.high}. + * Note that two intervals are equal if {@code i.low = i'.low} and {@code i.high = i'.high}. * * @param obj object to which this interval is to be compared * - * @return {@code true} if and only if the specified {@code Object} is a - * {@code Interval} whose low and high are equal to this - * {@code Interval's}. + * @return {@code true} if and only if the specified {@code Object} is a {@code Interval} whose low and high are + * equal to this {@code Interval's}. * */ @Override diff --git a/store/src/main/java/org/gephi/graph/api/Node.java b/src/main/java/org/gephi/graph/api/Node.java similarity index 100% rename from store/src/main/java/org/gephi/graph/api/Node.java rename to src/main/java/org/gephi/graph/api/Node.java diff --git a/store/src/main/java/org/gephi/graph/api/NodeIterable.java b/src/main/java/org/gephi/graph/api/NodeIterable.java similarity index 72% rename from store/src/main/java/org/gephi/graph/api/NodeIterable.java rename to src/main/java/org/gephi/graph/api/NodeIterable.java index fab69126..e25b0fd2 100644 --- a/store/src/main/java/org/gephi/graph/api/NodeIterable.java +++ b/src/main/java/org/gephi/graph/api/NodeIterable.java @@ -19,6 +19,9 @@ import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; /** * A node iterable. @@ -54,6 +57,27 @@ public interface NodeIterable extends ElementIterable { @Override public Collection toCollection(); + /** + * Returns the iterator content as a set. + * + * @return node set + */ + @Override + public Set toSet(); + + /** + * Returns a Spliterator over the nodes. + *

+ * Implementations return a splittable, sized, fail-fast spliterator suitable for parallel streams. When not + * possible, a non-splittable spliterator is returned. + * + * @return node spliterator + */ + @Override + default Spliterator spliterator() { + return ElementIterable.super.spliterator(); + } + /** * Empty node iterable. */ @@ -79,6 +103,11 @@ public Iterator iterator() { return this; } + @Override + public Spliterator spliterator() { + return Spliterators.emptySpliterator(); + } + @Override public Node[] toArray() { return new Node[0]; @@ -89,6 +118,11 @@ public Collection toCollection() { return Collections.EMPTY_LIST; } + @Override + public Set toSet() { + return Collections.EMPTY_SET; + } + @Override public void doBreak() { } diff --git a/store/src/main/java/org/gephi/graph/api/NodeProperties.java b/src/main/java/org/gephi/graph/api/NodeProperties.java similarity index 89% rename from store/src/main/java/org/gephi/graph/api/NodeProperties.java rename to src/main/java/org/gephi/graph/api/NodeProperties.java index 7d7b691e..5f07c9e8 100644 --- a/store/src/main/java/org/gephi/graph/api/NodeProperties.java +++ b/src/main/java/org/gephi/graph/api/NodeProperties.java @@ -69,6 +69,7 @@ public interface NodeProperties extends ElementProperties { * Sets the x position. * * @param x the x position + * @throws IllegalArgumentException if x is NaN */ public void setX(float x); @@ -76,6 +77,7 @@ public interface NodeProperties extends ElementProperties { * Sets the y position. * * @param y the y position + * @throws IllegalArgumentException if y is NaN */ public void setY(float y); @@ -83,6 +85,7 @@ public interface NodeProperties extends ElementProperties { * Sets the z position. * * @param z the z position + * @throws IllegalArgumentException if z is NaN */ public void setZ(float z); @@ -90,6 +93,7 @@ public interface NodeProperties extends ElementProperties { * Sets the size. * * @param size the size + * @throws IllegalArgumentException if size is NaN */ public void setSize(float size); @@ -98,6 +102,7 @@ public interface NodeProperties extends ElementProperties { * * @param x the x position * @param y the y position + * @throws IllegalArgumentException if x or y is NaN */ public void setPosition(float x, float y); @@ -107,6 +112,7 @@ public interface NodeProperties extends ElementProperties { * @param x the x position * @param y the y position * @param z the z position + * @throws IllegalArgumentException if x, y or z is NaN */ public void setPosition(float x, float y, float z); diff --git a/store/src/main/java/org/gephi/graph/api/Origin.java b/src/main/java/org/gephi/graph/api/Origin.java similarity index 100% rename from store/src/main/java/org/gephi/graph/api/Origin.java rename to src/main/java/org/gephi/graph/api/Origin.java diff --git a/src/main/java/org/gephi/graph/api/Rect2D.java b/src/main/java/org/gephi/graph/api/Rect2D.java new file mode 100644 index 00000000..5996a2df --- /dev/null +++ b/src/main/java/org/gephi/graph/api/Rect2D.java @@ -0,0 +1,231 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.api; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * Represents a 2D axis-aligned immutable rectangle. + * + * @author Eduardo Ramos + */ +public class Rect2D { + + public final float minX, minY; + public final float maxX, maxY; + + /** + * Create a new {@link Rect2D} as a copy of the given source . + * + * @param source the {@link Rect2D} to copy from + */ + public Rect2D(Rect2D source) { + this.minX = source.minX; + this.minY = source.minY; + this.maxX = source.maxX; + this.maxY = source.maxY; + } + + /** + * Create a new {@link Rect2D} with the given minimum and maximum corner coordinates. + * + * @param minX the x coordinate of the minimum corner + * @param minY the y coordinate of the minimum corner + * @param maxX the x coordinate of the maximum corner + * @param maxY the y coordinate of the maximum corner + */ + public Rect2D(float minX, float minY, float maxX, float maxY) { + if (minX > maxX) { + throw new IllegalArgumentException("minX > maxX"); + } + + if (minY > maxY) { + throw new IllegalArgumentException("minX > maxX"); + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * Return the rectangle's width. + * + * @return the rectangle's width + */ + public float width() { + return maxX - minX; + } + + /** + * Return the rectangle's height. + * + * @return the rectangle's height + */ + public float height() { + return maxY - minY; + } + + /** + * Return the rectangle's center, as an array where the first element is the x coordinate and the second element is + * the y coordinate. + * + * @return the rectangle's center + */ + public float[] center() { + return new float[] { (maxX + minX) / 2, (maxY + minY) / 2 }; + } + + /** + * Return the rectangle's radius. + * + * @return the rectangle's radius + */ + public float radius() { + float width = width(); + float height = height(); + return (float) Math.sqrt(width * width + height * height) / 2; + } + + private static final DecimalFormat FORMAT = new DecimalFormat("0.###", + DecimalFormatSymbols.getInstance(Locale.ENGLISH)); + + @Override + public String toString() { + return toString(FORMAT); + } + + private String toString(NumberFormat formatter) { + return "min(x:" + formatter.format(minX) + " y:" + formatter.format(minY) + ") < " + "max(x:" + formatter + .format(maxX) + " y:" + formatter.format(maxY) + ")"; + } + + /** + * Returns true if this rectangle contains the given rectangle. + * + * @param rect the rectangle to check + * @return true if this rectangle contains, false otherwise + */ + public boolean contains(Rect2D rect) { + if (rect == this) { + return true; + } + + return contains(rect.minX, rect.minY, rect.maxX, rect.maxY); + } + + /** + * Returns true if this rectangle intersects the given rectangle. + * + * @param rect the rectangle to check + * @return true if this rectangle intersects, false otherwise + */ + public boolean intersects(Rect2D rect) { + if (rect == this) { + return true; + } + + return intersects(rect.minX, rect.minY, rect.maxX, rect.maxY); + } + + /** + * Returns true if this rectangle contains the given rectangle. + * + * @param minX the x coordinate of the minimum corner + * @param minY the y coordinate of the minimum corner + * @param maxX the x coordinate of the maximum corner + * @param maxY the y coordinate of the maximum corner + * + * @return true if this rectangle contains, false otherwise + */ + public boolean contains(float minX, float minY, float maxX, float maxY) { + return this.minX <= minX && this.minY <= minY && this.maxX >= maxX && this.maxY >= maxY; + } + + /** + * Returns true if this rectangle intersects the given rectangle. + * + * @param minX the x coordinate of the minimum corner + * @param minY the y coordinate of the minimum corner + * @param maxX the x coordinate of the maximum corner + * @param maxY the y coordinate of the maximum corner + * + * @return true if this rectangle intersects, false otherwise + */ + public boolean intersects(float minX, float minY, float maxX, float maxY) { + return this.minX <= maxX && minX <= this.maxX && this.maxY >= minY && maxY >= this.minY; + } + + /** + * Returns true if this rectangle contains or intersects with the given rectangle. This is equivalent to checking + * {@code this.contains(rect) || this.intersects(rect)} but more efficient as it performs the check in a single + * operation. + * + * @param rect the rectangle to check + * @return true if this rectangle contains or intersects with the given rectangle, false otherwise + */ + public boolean containsOrIntersects(Rect2D rect) { + if (rect == this) { + return true; + } + + return containsOrIntersects(rect.minX, rect.minY, rect.maxX, rect.maxY); + } + + /** + * Returns true if this rectangle contains or intersects with the given rectangle. This is equivalent to checking + * {@code this.contains(minX, minY, maxX, maxY) || this.intersects(minX, minY, maxX, maxY)} but more efficient as it + * performs the check in a single operation. + * + * @param minX the x coordinate of the minimum corner + * @param minY the y coordinate of the minimum corner + * @param maxX the x coordinate of the maximum corner + * @param maxY the y coordinate of the maximum corner + * + * @return true if this rectangle contains or intersects with the given rectangle, false otherwise + */ + public boolean containsOrIntersects(float minX, float minY, float maxX, float maxY) { + // Two rectangles have overlap if they intersect - containment is a subset of + // intersection + return this.minX <= maxX && minX <= this.maxX && this.maxY >= minY && maxY >= this.minY; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Rect2D rect2D = (Rect2D) obj; + return Float.compare(rect2D.minX, minX) == 0 && Float.compare(rect2D.minY, minY) == 0 && Float + .compare(rect2D.maxX, maxX) == 0 && Float.compare(rect2D.maxY, maxY) == 0; + } + + @Override + public int hashCode() { + int result = (minX != +0.0f ? Float.floatToIntBits(minX) : 0); + result = 31 * result + (minY != +0.0f ? Float.floatToIntBits(minY) : 0); + result = 31 * result + (maxX != +0.0f ? Float.floatToIntBits(maxX) : 0); + result = 31 * result + (maxY != +0.0f ? Float.floatToIntBits(maxY) : 0); + return result; + } +} diff --git a/src/main/java/org/gephi/graph/api/SpatialIndex.java b/src/main/java/org/gephi/graph/api/SpatialIndex.java new file mode 100644 index 00000000..95bc3e6b --- /dev/null +++ b/src/main/java/org/gephi/graph/api/SpatialIndex.java @@ -0,0 +1,136 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.api; + +import java.util.function.Predicate; + +/** + * Query the (quadtree-based) index based on the given rectangle area. + *

+ * The spatial index is not enabled by default. To enable it, set the appropriate configuration: + * @{@link Configuration.Builder#enableSpatialIndex(boolean)}. + *

+ * When nodes are moved, added or removed, the spatial index is automatically updated. Edges are not indexed, but they + * are queried based on whether their source or target nodes are in the given area. + *

+ * The Z position is not taken into account when querying the spatial index, only X/Y are supported. + *

+ * + * @author Eduardo Ramos + */ +public interface SpatialIndex { + + /** + * Returns the nodes in the given area. + * + * @param rect area to query + * @return nodes in the area + */ + NodeIterable getNodesInArea(Rect2D rect); + + /** + * Returns the nodes in the given area, filtered by the given predicate. + * + * @param rect area to query + * @param predicate filter predicate + * @return nodes in the area + */ + NodeIterable getNodesInArea(Rect2D rect, Predicate predicate); + + /** + * Returns the nodes in the given area using a faster, but approximate method. + *

+ * All nodes in the provided area are guaranteed to be returned, but some nodes outside the area may also be + * returned. + * + * @param rect area to query + * @return nodes in the area + */ + NodeIterable getApproximateNodesInArea(Rect2D rect); + + /** + * Returns the nodes in the given area using a faster, but approximate method, filtered by the given predicate. + *

+ * All nodes in the provided area are guaranteed to be returned, but some nodes outside the area may also be + * returned. + * + * @param rect area to query + * @param predicate filter predicate + * @return nodes in the area + */ + NodeIterable getApproximateNodesInArea(Rect2D rect, Predicate predicate); + + /** + * Returns the edges in the given area. Edges may be returned twice. + * + * @param rect area to query + * @return edges in the area + */ + EdgeIterable getEdgesInArea(Rect2D rect); + + /** + * Returns the edges in the given area, filtered by the given predicate. Edges may be returned twice. + * + * @param rect area to query + * @param predicate filter predicate + * @return edges in the area + */ + EdgeIterable getEdgesInArea(Rect2D rect, Predicate predicate); + + /** + * Returns the edges in the given area using a faster, but approximate method. + *

+ * All edges in the provided area are guaranteed to be returned, but some edges outside the area may also be + * returned. Edges may also be returned twice. + * + * @param rect area to query + * @return edges in the area + */ + EdgeIterable getApproximateEdgesInArea(Rect2D rect); + + /** + * Returns the edges in the given area using a faster, but approximate method, filtered by the given predicate. + *

+ * All edges in the provided area are guaranteed to be returned, but some edges outside the area may also be + * returned. Edges may also be returned twice. + * + * @param rect area to query + * @param predicate filter predicate + * @return edges in the area + */ + EdgeIterable getApproximateEdgesInArea(Rect2D rect, Predicate predicate); + + /** + * Returns the bounding rectangle that contains all nodes in the graph. The boundaries are calculated based on each + * node's position and size. + * + * @return the bounding rectangle, or null if there are no nodes + */ + Rect2D getBoundaries(); + + /** + * Acquires a read lock on the spatial index. This is recommended when using the query functions in a stream + * context, to avoid the spatial index being modified while being queried. + *

+ * Every call to this method must be matched with a call to {@link #spatialIndexReadUnlock()}. + */ + void spatialIndexReadLock(); + + /** + * Releases a read lock on the spatial index. This must be called after a call to {@link #spatialIndexReadLock()}. + */ + void spatialIndexReadUnlock(); +} diff --git a/store/src/main/java/org/gephi/graph/api/Subgraph.java b/src/main/java/org/gephi/graph/api/Subgraph.java similarity index 79% rename from store/src/main/java/org/gephi/graph/api/Subgraph.java rename to src/main/java/org/gephi/graph/api/Subgraph.java index 00e88a8b..3b7671b6 100644 --- a/store/src/main/java/org/gephi/graph/api/Subgraph.java +++ b/src/main/java/org/gephi/graph/api/Subgraph.java @@ -20,11 +20,10 @@ /** * A subgraph is a subset of a graph based on a graph view. *

- * A subgraph has the same or less elements compared to the graph it's based on. - * This interface inherits from Graph and all read operations behave in - * a similar fashion. For instance, calling getNodes will return only - * nodes in this subgraph. However, write operations such as addNode or - * removeNode are used to control which elements are part of the view. + * A subgraph has the same or less elements compared to the graph it's based on. This interface inherits from + * Graph and all read operations behave in a similar fashion. For instance, calling getNodes will + * return only nodes in this subgraph. However, write operations such as addNode or removeNode are + * used to control which elements are part of the view. * */ public interface Subgraph extends Graph { @@ -110,6 +109,16 @@ public interface Subgraph extends Graph { @Override public boolean removeAllNodes(Collection nodes); + /** + * Retains only nodes in this subgraph that are contained in the specified collection. + *

+ * The nodes should be part of the root graph. + * + * @param nodes the node collection + * @return true if at least one node has been removed, false otherwise + */ + public boolean retainNodes(Collection nodes); + /** * Removes an edge from this subgraph. *

@@ -132,6 +141,16 @@ public interface Subgraph extends Graph { @Override public boolean removeAllEdges(Collection edges); + /** + * Retains only edges in this subgraph that are contained in the specified collection. + *

+ * The edges should be part of the root graph. + * + * @param edges the edge collection + * @return true if at least one edge has been removed, false otherwise + */ + public boolean retainEdges(Collection edges); + /** * Fills the subgraph so all elements in the graph are in the subgraph. */ @@ -156,8 +175,7 @@ public interface Subgraph extends Graph { public void intersection(Subgraph subGraph); /** - * Inverse this subgraph so all elements in the graph are removed and all - * elements not in the graph are added. + * Inverse this subgraph so all elements in the graph are removed and all elements not in the graph are added. */ public void not(); } diff --git a/store/src/main/java/org/gephi/graph/api/Table.java b/src/main/java/org/gephi/graph/api/Table.java similarity index 83% rename from store/src/main/java/org/gephi/graph/api/Table.java rename to src/main/java/org/gephi/graph/api/Table.java index 3112c428..2db59acc 100644 --- a/store/src/main/java/org/gephi/graph/api/Table.java +++ b/src/main/java/org/gephi/graph/api/Table.java @@ -13,11 +13,11 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.gephi.graph.api; /** - * The table is the container for columns. Column ids in all methods are - * converted to lower case. + * The table is the container for columns. Column ids in all methods are converted to lower case. */ public interface Table extends ColumnIterable { @@ -109,6 +109,14 @@ public interface Table extends ColumnIterable { */ public int countColumns(); + /** + * Counts the columns of the given origin. + * + * @param origin the origin + * @return the number of columns set with this origin + */ + public int countColumns(Origin origin); + /** * The element class of this column. * @@ -130,4 +138,25 @@ public interface Table extends ColumnIterable { * @return graph */ public Graph getGraph(); + + /** + * Returns true if this table is the node table. + * + * @return true if node table, false otherwise + */ + public boolean isNodeTable(); + + /** + * Returns true if this table is the node table. + * + * @return true if node table, false otherwise + */ + public boolean isEdgeTable(); + + /** + * Returns the table lock, which controls the multi-thread access to the table. + * + * @return table lock + */ + TableLock getLock(); } diff --git a/store/src/main/java/org/gephi/graph/api/TableDiff.java b/src/main/java/org/gephi/graph/api/TableDiff.java similarity index 95% rename from store/src/main/java/org/gephi/graph/api/TableDiff.java rename to src/main/java/org/gephi/graph/api/TableDiff.java index 2ad3f944..1e29ef83 100644 --- a/store/src/main/java/org/gephi/graph/api/TableDiff.java +++ b/src/main/java/org/gephi/graph/api/TableDiff.java @@ -20,8 +20,8 @@ /** * Interface to retrieve added, removed and modified columns from the table. *

- * This interface is associated with a {@link TableObserver} and provides an - * easy access to the columns added or removed. + * This interface is associated with a {@link TableObserver} and provides an easy access to the columns added or + * removed. */ public interface TableDiff { diff --git a/src/main/java/org/gephi/graph/api/TableLock.java b/src/main/java/org/gephi/graph/api/TableLock.java new file mode 100644 index 00000000..b8b79c46 --- /dev/null +++ b/src/main/java/org/gephi/graph/api/TableLock.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.api; + +public interface TableLock { + + /** + * Acquires the lock. Acquires the lock if it is not held by another thread and returns immediately, setting the + * lock hold count to one. + */ + void lock(); + + /** + * Attempts to release this lock. If the current thread is the holder of this lock then the hold count is + * decremented. If the hold count is now zero then the lock is released. If the current thread is not the holder of + * this lock then IllegalMonitorStateException is thrown. + * + * @throws IllegalMonitorStateException if the current thread does not hold this lock + */ + void unlock(); + + /** + * Queries the number of holds on this lock by the current thread. A thread has a hold on a lock for each lock + * action that is not matched by an unlock action. + * + * @return the number of holds on this lock by the current thread, or zero if this lock is not held by the current + * thread + */ + int getHoldCount(); +} diff --git a/store/src/main/java/org/gephi/graph/api/TableObserver.java b/src/main/java/org/gephi/graph/api/TableObserver.java similarity index 74% rename from store/src/main/java/org/gephi/graph/api/TableObserver.java rename to src/main/java/org/gephi/graph/api/TableObserver.java index 8aab4381..bebbad5c 100644 --- a/store/src/main/java/org/gephi/graph/api/TableObserver.java +++ b/src/main/java/org/gephi/graph/api/TableObserver.java @@ -18,18 +18,16 @@ /** * Observer over a table to monitor changes. *

- * The table observer is a mechanism used to monitor periodically changes made - * to the table. This scenario is common in multi-threaded application where a - * thread is modifying the table and one or multiple threads need to take action - * when updates are made. + * The table observer is a mechanism used to monitor periodically changes made to the table. This scenario is common in + * multi-threaded application where a thread is modifying the table and one or multiple threads need to take action when + * updates are made. *

- * Table observer users should periodically call the - * hasTableChanged() method to check the status. Each call resets - * the observer so if the method returns true and the table doesn't change after - * that it will return false next time. + * Table observer users should periodically call the hasTableChanged() method to check the status. Each + * call resets the observer so if the method returns true and the table doesn't change after that it will return false + * next time. *

- * Observers should be destroyed when not needed anymore. A new observer can be - * obtained from the Table instance. + * Observers should be destroyed when not needed anymore. A new observer can be obtained from the Table + * instance. * * @see Table */ diff --git a/store/src/main/java/org/gephi/graph/api/TextProperties.java b/src/main/java/org/gephi/graph/api/TextProperties.java similarity index 100% rename from store/src/main/java/org/gephi/graph/api/TextProperties.java rename to src/main/java/org/gephi/graph/api/TextProperties.java diff --git a/store/src/main/java/org/gephi/graph/api/TimeFormat.java b/src/main/java/org/gephi/graph/api/TimeFormat.java similarity index 100% rename from store/src/main/java/org/gephi/graph/api/TimeFormat.java rename to src/main/java/org/gephi/graph/api/TimeFormat.java diff --git a/store/src/main/java/org/gephi/graph/api/TimeIndex.java b/src/main/java/org/gephi/graph/api/TimeIndex.java similarity index 100% rename from store/src/main/java/org/gephi/graph/api/TimeIndex.java rename to src/main/java/org/gephi/graph/api/TimeIndex.java diff --git a/store/src/main/java/org/gephi/graph/api/TimeRepresentation.java b/src/main/java/org/gephi/graph/api/TimeRepresentation.java similarity index 69% rename from store/src/main/java/org/gephi/graph/api/TimeRepresentation.java rename to src/main/java/org/gephi/graph/api/TimeRepresentation.java index 280e8a04..af3a5b2e 100644 --- a/store/src/main/java/org/gephi/graph/api/TimeRepresentation.java +++ b/src/main/java/org/gephi/graph/api/TimeRepresentation.java @@ -18,15 +18,13 @@ /** * Different time representations. *

- * Both the elements (i.e nodes and edges) existence in time and the attributes' - * values in time can be represented in two different ways: using timestamps or - * using intervals. They can be mixed thought and therefore need to be + * Both the elements (i.e nodes and edges) existence in time and the attributes' values in time can be represented in + * two different ways: using timestamps or using intervals. They can be mixed thought and therefore need to be * configured by the user. *

- * Each representation has its advantages and disadvantages. For instance, - * timestamps are great when observations are made at fixed periods. On the - * other hand, intervals are great when the time is arbitrary and elements or - * attributes have long continuous existence. + * Each representation has its advantages and disadvantages. For instance, timestamps are great when observations are + * made at fixed periods. On the other hand, intervals are great when the time is arbitrary and elements or attributes + * have long continuous existence. * * @see Configuration */ @@ -34,16 +32,14 @@ public enum TimeRepresentation { /** * Timestamp representation (fixed). *

- * Time is represented using timestamps. Timestamps are single value and - * represent a single moment in time. + * Time is represented using timestamps. Timestamps are single value and represent a single moment in time. */ TIMESTAMP, /** * Interval representation (continuous). *

- * Time is represented using intervals, with a beginning and an end. - * Intervals are always included on both bounds but allows an infinite - * bound. + * Time is represented using intervals, with a beginning and an end. Intervals are always included on both bounds + * but allows an infinite bound. */ INTERVAL; } diff --git a/store/src/main/java/org/gephi/graph/api/UndirectedGraph.java b/src/main/java/org/gephi/graph/api/UndirectedGraph.java similarity index 100% rename from store/src/main/java/org/gephi/graph/api/UndirectedGraph.java rename to src/main/java/org/gephi/graph/api/UndirectedGraph.java diff --git a/store/src/main/java/org/gephi/graph/api/UndirectedSubgraph.java b/src/main/java/org/gephi/graph/api/UndirectedSubgraph.java similarity index 100% rename from store/src/main/java/org/gephi/graph/api/UndirectedSubgraph.java rename to src/main/java/org/gephi/graph/api/UndirectedSubgraph.java diff --git a/src/main/java/org/gephi/graph/api/UnsupportedFormatVersionException.java b/src/main/java/org/gephi/graph/api/UnsupportedFormatVersionException.java new file mode 100644 index 00000000..649172ce --- /dev/null +++ b/src/main/java/org/gephi/graph/api/UnsupportedFormatVersionException.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.api; + +import java.io.IOException; + +/** + * Thrown when reading a serialized graph model written by a newer, incompatible version of graphstore than the one + * doing the reading. + *

+ * Extends {@link IOException} so it is caught by existing code that only handles I/O errors, while callers that want to + * report a specific, localized message can catch this type directly and use {@link #getFileVersion()} and + * {@link #getMaxSupportedVersion()} instead of parsing the message. + */ +public class UnsupportedFormatVersionException extends IOException { + + private final float fileVersion; + private final float maxSupportedVersion; + + public UnsupportedFormatVersionException(float fileVersion, float maxSupportedVersion) { + super("Unsupported serialization format version: " + fileVersion + ". This file was written by a newer version of graphstore than this library supports (up to " + maxSupportedVersion + "). Please upgrade graphstore to read this file."); + this.fileVersion = fileVersion; + this.maxSupportedVersion = maxSupportedVersion; + } + + /** + * Returns the format version the file was written with. + * + * @return file format version + */ + public float getFileVersion() { + return fileVersion; + } + + /** + * Returns the highest format version this version of graphstore can read. + * + * @return max supported format version + */ + public float getMaxSupportedVersion() { + return maxSupportedVersion; + } +} diff --git a/src/main/java/org/gephi/graph/api/package.html b/src/main/java/org/gephi/graph/api/package.html new file mode 100644 index 00000000..ae5d4a6f --- /dev/null +++ b/src/main/java/org/gephi/graph/api/package.html @@ -0,0 +1,8 @@ + + + + Complete API description, where + GraphModel + is the entry point. + + diff --git a/store/src/main/java/org/gephi/graph/api/types/IntervalBooleanMap.java b/src/main/java/org/gephi/graph/api/types/IntervalBooleanMap.java similarity index 92% rename from store/src/main/java/org/gephi/graph/api/types/IntervalBooleanMap.java rename to src/main/java/org/gephi/graph/api/types/IntervalBooleanMap.java index 7f962d52..3773b414 100644 --- a/store/src/main/java/org/gephi/graph/api/types/IntervalBooleanMap.java +++ b/src/main/java/org/gephi/graph/api/types/IntervalBooleanMap.java @@ -38,8 +38,8 @@ public IntervalBooleanMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -51,8 +51,7 @@ public IntervalBooleanMap(int capacity) { /** * Constructor with an initial interval map. *

- * The keys array must be in the same format returned by - * {@link #getIntervals() }. + * The keys array must be in the same format returned by {@link #getIntervals() }. * * @param keys initial keys content * @param vals initial values content @@ -63,6 +62,15 @@ public IntervalBooleanMap(double[] keys, boolean[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public IntervalBooleanMap(IntervalBooleanMap source) { + this(source.array, source.values); + } + /** * Get the value for the given interval. * @@ -98,8 +106,8 @@ public boolean getBoolean(Interval interval, boolean defaultValue) { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/IntervalByteMap.java b/src/main/java/org/gephi/graph/api/types/IntervalByteMap.java similarity index 91% rename from store/src/main/java/org/gephi/graph/api/types/IntervalByteMap.java rename to src/main/java/org/gephi/graph/api/types/IntervalByteMap.java index 80b25d38..5bf835c7 100644 --- a/store/src/main/java/org/gephi/graph/api/types/IntervalByteMap.java +++ b/src/main/java/org/gephi/graph/api/types/IntervalByteMap.java @@ -38,8 +38,8 @@ public IntervalByteMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -51,8 +51,7 @@ public IntervalByteMap(int capacity) { /** * Constructor with an initial interval map. *

- * The keys array must be in the same format returned by - * {@link #getIntervals() }. + * The keys array must be in the same format returned by {@link #getIntervals() }. * * @param keys initial keys content * @param vals initial values content @@ -63,6 +62,15 @@ public IntervalByteMap(double[] keys, byte[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public IntervalByteMap(IntervalByteMap source) { + this(source.array, source.values); + } + /** * Get the value for the given interval. * @@ -98,8 +106,8 @@ public byte getByte(Interval interval, byte defaultValue) { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/IntervalCharMap.java b/src/main/java/org/gephi/graph/api/types/IntervalCharMap.java similarity index 90% rename from store/src/main/java/org/gephi/graph/api/types/IntervalCharMap.java rename to src/main/java/org/gephi/graph/api/types/IntervalCharMap.java index 5353cdec..affcf8d1 100644 --- a/store/src/main/java/org/gephi/graph/api/types/IntervalCharMap.java +++ b/src/main/java/org/gephi/graph/api/types/IntervalCharMap.java @@ -38,8 +38,8 @@ public IntervalCharMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -51,8 +51,7 @@ public IntervalCharMap(int capacity) { /** * Constructor with an initial interval map. *

- * The keys array must be in the same format returned by - * {@link #getIntervals() }. + * The keys array must be in the same format returned by {@link #getIntervals() }. * * @param keys initial keys content * @param vals initial values content @@ -63,6 +62,15 @@ public IntervalCharMap(double[] keys, char[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public IntervalCharMap(IntervalCharMap source) { + this(source.array, source.values); + } + /** * Get the value for the given interval. * @@ -98,8 +106,8 @@ public char getCharacter(Interval interval, char defaultValue) { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/IntervalDoubleMap.java b/src/main/java/org/gephi/graph/api/types/IntervalDoubleMap.java similarity index 90% rename from store/src/main/java/org/gephi/graph/api/types/IntervalDoubleMap.java rename to src/main/java/org/gephi/graph/api/types/IntervalDoubleMap.java index ae29a31f..15dd2cc9 100644 --- a/store/src/main/java/org/gephi/graph/api/types/IntervalDoubleMap.java +++ b/src/main/java/org/gephi/graph/api/types/IntervalDoubleMap.java @@ -38,8 +38,8 @@ public IntervalDoubleMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -51,8 +51,7 @@ public IntervalDoubleMap(int capacity) { /** * Constructor with an initial interval map. *

- * The keys array must be in the same format returned by - * {@link #getIntervals() }. + * The keys array must be in the same format returned by {@link #getIntervals() }. * * @param keys initial keys content * @param vals initial values content @@ -63,6 +62,15 @@ public IntervalDoubleMap(double[] keys, double[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public IntervalDoubleMap(IntervalDoubleMap source) { + this(source.array, source.values); + } + /** * Get the value for the given interval. * @@ -98,8 +106,8 @@ public double getDouble(Interval interval, double defaultValue) { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/IntervalFloatMap.java b/src/main/java/org/gephi/graph/api/types/IntervalFloatMap.java similarity index 91% rename from store/src/main/java/org/gephi/graph/api/types/IntervalFloatMap.java rename to src/main/java/org/gephi/graph/api/types/IntervalFloatMap.java index 15dd5e85..9092f7a4 100644 --- a/store/src/main/java/org/gephi/graph/api/types/IntervalFloatMap.java +++ b/src/main/java/org/gephi/graph/api/types/IntervalFloatMap.java @@ -39,8 +39,8 @@ public IntervalFloatMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -52,8 +52,7 @@ public IntervalFloatMap(int capacity) { /** * Constructor with an initial interval map. *

- * The keys array must be in the same format returned by - * {@link #getIntervals() }. + * The keys array must be in the same format returned by {@link #getIntervals() }. * * @param keys initial keys content * @param vals initial values content @@ -64,6 +63,15 @@ public IntervalFloatMap(double[] keys, float[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public IntervalFloatMap(IntervalFloatMap source) { + this(source.array, source.values); + } + /** * Get the value for the given interval. * @@ -99,8 +107,8 @@ public float getFloat(Interval interval, float defaultValue) { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/IntervalIntegerMap.java b/src/main/java/org/gephi/graph/api/types/IntervalIntegerMap.java similarity index 91% rename from store/src/main/java/org/gephi/graph/api/types/IntervalIntegerMap.java rename to src/main/java/org/gephi/graph/api/types/IntervalIntegerMap.java index 4ccbc4e6..31c12bf5 100644 --- a/store/src/main/java/org/gephi/graph/api/types/IntervalIntegerMap.java +++ b/src/main/java/org/gephi/graph/api/types/IntervalIntegerMap.java @@ -38,8 +38,8 @@ public IntervalIntegerMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -51,8 +51,7 @@ public IntervalIntegerMap(int capacity) { /** * Constructor with an initial interval map. *

- * The keys array must be in the same format returned by - * {@link #getIntervals() }. + * The keys array must be in the same format returned by {@link #getIntervals() }. * * @param keys initial keys content * @param vals initial values content @@ -63,6 +62,15 @@ public IntervalIntegerMap(double[] keys, int[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public IntervalIntegerMap(IntervalIntegerMap source) { + this(source.array, source.values); + } + /** * Get the value for the given interval. * @@ -98,8 +106,8 @@ public int getInteger(Interval interval, int defaultValue) { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/IntervalLongMap.java b/src/main/java/org/gephi/graph/api/types/IntervalLongMap.java similarity index 91% rename from store/src/main/java/org/gephi/graph/api/types/IntervalLongMap.java rename to src/main/java/org/gephi/graph/api/types/IntervalLongMap.java index 40d397e6..9126ae5a 100644 --- a/store/src/main/java/org/gephi/graph/api/types/IntervalLongMap.java +++ b/src/main/java/org/gephi/graph/api/types/IntervalLongMap.java @@ -38,8 +38,8 @@ public IntervalLongMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -51,8 +51,7 @@ public IntervalLongMap(int capacity) { /** * Constructor with an initial interval map. *

- * The keys array must be in the same format returned by - * {@link #getIntervals() }. + * The keys array must be in the same format returned by {@link #getIntervals() }. * * @param keys initial keys content * @param vals initial values content @@ -63,6 +62,15 @@ public IntervalLongMap(double[] keys, long[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public IntervalLongMap(IntervalLongMap source) { + this(source.array, source.values); + } + /** * Get the value for the given interval. * @@ -98,8 +106,8 @@ public long getLong(Interval interval, long defaultValue) { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/IntervalMap.java b/src/main/java/org/gephi/graph/api/types/IntervalMap.java similarity index 95% rename from store/src/main/java/org/gephi/graph/api/types/IntervalMap.java rename to src/main/java/org/gephi/graph/api/types/IntervalMap.java index adbcdc88..8c0c026c 100644 --- a/store/src/main/java/org/gephi/graph/api/types/IntervalMap.java +++ b/src/main/java/org/gephi/graph/api/types/IntervalMap.java @@ -16,22 +16,21 @@ package org.gephi.graph.api.types; import java.lang.reflect.Array; -import org.gephi.graph.api.Estimator; import java.math.BigDecimal; import java.math.RoundingMode; +import java.time.ZoneId; import java.util.Arrays; import org.gephi.graph.api.AttributeUtils; +import org.gephi.graph.api.Estimator; import org.gephi.graph.api.Interval; import org.gephi.graph.api.TimeFormat; import org.gephi.graph.impl.FormattingAndParsingUtils; -import org.joda.time.DateTimeZone; /** - * Abstract class that implement a sorted map between intervals and attribute - * values. + * Abstract class that implement a sorted map between intervals and attribute values. *

- * Implementations which extend this class customize the map for a unique type, - * which is represented by the T parameter. + * Implementations which extend this class customize the map for a unique type, which is represented by the + * T parameter. * * @param Value type */ @@ -52,8 +51,8 @@ public IntervalMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -283,8 +282,8 @@ protected int removeInner(double intervalStart, double intervalEnd) { if (startValue == intervalStart && endValue > intervalEnd) { return -1; } - if ((shift = (intervalEnd > endValue ? 2 : intervalEnd < endValue ? -2 : intervalStart > startValue ? 2 - : 0)) == 0) { + if ((shift = (intervalEnd > endValue ? 2 + : intervalEnd < endValue ? -2 : intervalStart > startValue ? 2 : 0)) == 0) { if (removeIndex == realSize - 2) { size--; } else { @@ -310,8 +309,7 @@ public boolean isEmpty() { } /** - * Returns true if this map contains an interval that starts or ends at - * timestamp. + * Returns true if this map contains an interval that starts or ends at timestamp. * * @param timestamp timestamp * @return true if contains, false otherwise @@ -337,8 +335,8 @@ protected int getIndex(double intervalStart, double intervalEnd) { if (startValue == intervalStart && endValue > intervalEnd) { return -1; } - if ((shift = (intervalEnd > endValue ? 2 : intervalEnd < endValue ? -2 : intervalStart > startValue ? 2 - : 0)) == 0) { + if ((shift = (intervalEnd > endValue ? 2 + : intervalEnd < endValue ? -2 : intervalStart > startValue ? 2 : 0)) == 0) { return foundIndex; } } @@ -418,11 +416,11 @@ public Interval[] toKeysArray() { /** * Returns an array of all intervals in this set. *

- * The intervals are represented in a flat and sorted array (e.g. - * {[1.0,2.0], [5.0,6.0]}) returns [1.0,2.0,5.0,6.0]). + * The intervals are represented in a flat and sorted array (e.g. {[1.0,2.0], [5.0,6.0]}) returns + * [1.0,2.0,5.0,6.0]). *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all intervals */ @@ -604,7 +602,7 @@ protected Double getAverageDouble(final Interval interval) { } @Override - public String toString(TimeFormat timeFormat, DateTimeZone timeZone) { + public String toString(TimeFormat timeFormat, ZoneId zoneId) { if (size == 0) { return FormattingAndParsingUtils.EMPTY_VALUE; } @@ -615,13 +613,14 @@ public String toString(TimeFormat timeFormat, DateTimeZone timeZone) { sb.append('<'); for (int i = 0; i < size; i++) { sb.append('['); - sb.append(AttributeUtils.printTimestampInFormat(array[i * 2], timeFormat, timeZone)); + sb.append(AttributeUtils.printTimestampInFormat(array[i * 2], timeFormat, zoneId)); sb.append(", "); - sb.append(AttributeUtils.printTimestampInFormat(array[i * 2 + 1], timeFormat, timeZone)); + sb.append(AttributeUtils.printTimestampInFormat(array[i * 2 + 1], timeFormat, zoneId)); sb.append(", "); String stringValue = values[i].toString(); - if (FormattingAndParsingUtils.containsDynamicSpecialCharacters(stringValue) || stringValue.trim().isEmpty()) { + if (FormattingAndParsingUtils.containsDynamicSpecialCharacters(stringValue) || stringValue.trim() + .isEmpty()) { sb.append('"'); sb.append(stringValue.replace("\\", "\\\\").replace("\"", "\\\"")); sb.append('"'); diff --git a/store/src/main/java/org/gephi/graph/api/types/IntervalSet.java b/src/main/java/org/gephi/graph/api/types/IntervalSet.java similarity index 88% rename from store/src/main/java/org/gephi/graph/api/types/IntervalSet.java rename to src/main/java/org/gephi/graph/api/types/IntervalSet.java index ef3210e3..e1ad1147 100644 --- a/store/src/main/java/org/gephi/graph/api/types/IntervalSet.java +++ b/src/main/java/org/gephi/graph/api/types/IntervalSet.java @@ -15,12 +15,12 @@ */ package org.gephi.graph.api.types; +import java.time.ZoneId; import java.util.Arrays; import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Interval; import org.gephi.graph.api.TimeFormat; import org.gephi.graph.impl.FormattingAndParsingUtils; -import org.joda.time.DateTimeZone; /** * Sorted set for intervals. @@ -42,8 +42,8 @@ public IntervalSet() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * intervals is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of intervals is known in advance as it minimizes + * array resizes. * * @param capacity interval capacity */ @@ -65,6 +65,15 @@ public IntervalSet(double[] arr) { size = arr.length / 2; } + /** + * Copy constructor. + * + * @param source set to copy + */ + public IntervalSet(IntervalSet source) { + this(source.array); + } + @Override public boolean add(Interval interval) { return addInner(interval.getLow(), interval.getHigh()) >= 0; @@ -85,9 +94,40 @@ public boolean isEmpty() { return size == 0; } + @Override + public Interval getMax() { + if (size > 0) { + return new Interval(array[array.length - 2], array[array.length - 1]); + } + return null; + } + + @Override + public Interval getMin() { + if (size > 0) { + return new Interval(array[0], array[1]); + } + return null; + } + + @Override + public Double getMaxDouble() { + if (size > 0) { + return array[array.length - 1]; + } + return null; + } + + @Override + public Double getMinDouble() { + if (size > 0) { + return array[0]; + } + return null; + } + /** - * Returns true if this set contains an interval that starts or ends at - * timestamp. + * Returns true if this set contains an interval that starts or ends at timestamp. * * @param timestamp timestamp * @return true if contains, false otherwise @@ -114,8 +154,8 @@ public boolean contains(Interval interval) { if (startValue == interval.getLow() && endValue > interval.getHigh()) { return false; } - if ((shift = (interval.getHigh() > endValue ? 2 : interval.getHigh() < endValue ? -2 : interval - .getLow() > startValue ? 2 : 0)) == 0) { + if ((shift = (interval.getHigh() > endValue ? 2 + : interval.getHigh() < endValue ? -2 : interval.getLow() > startValue ? 2 : 0)) == 0) { return true; } } @@ -126,11 +166,11 @@ public boolean contains(Interval interval) { /** * Returns an array of all intervals in this set in a flat format. *

- * The intervals are represented in a flat and sorted array (e.g. - * {[1.0,2.0], [5.0,6.0]}) returns [1.0,2.0,5.0,6.0]). + * The intervals are represented in a flat and sorted array (e.g. {[1.0,2.0], [5.0,6.0]}) returns + * [1.0,2.0,5.0,6.0]). *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all intervals */ @@ -262,8 +302,8 @@ private int removeInner(double intervalStart, double intervalEnd) { if (startValue == intervalStart && endValue > intervalEnd) { return -1; } - if ((shift = (intervalEnd > endValue ? 2 : intervalEnd < endValue ? -2 : intervalStart > startValue ? 2 - : 0)) == 0) { + if ((shift = (intervalEnd > endValue ? 2 + : intervalEnd < endValue ? -2 : intervalStart > startValue ? 2 : 0)) == 0) { if (removeIndex == realSize - 2) { size--; } else { @@ -319,7 +359,7 @@ public boolean equals(Object obj) { } @Override - public String toString(TimeFormat timeFormat, DateTimeZone timeZone) { + public String toString(TimeFormat timeFormat, ZoneId timeZone) { if (size == 0) { return FormattingAndParsingUtils.EMPTY_VALUE; } diff --git a/store/src/main/java/org/gephi/graph/api/types/IntervalShortMap.java b/src/main/java/org/gephi/graph/api/types/IntervalShortMap.java similarity index 91% rename from store/src/main/java/org/gephi/graph/api/types/IntervalShortMap.java rename to src/main/java/org/gephi/graph/api/types/IntervalShortMap.java index 62c04daf..3a0dc642 100644 --- a/store/src/main/java/org/gephi/graph/api/types/IntervalShortMap.java +++ b/src/main/java/org/gephi/graph/api/types/IntervalShortMap.java @@ -38,8 +38,8 @@ public IntervalShortMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -51,8 +51,7 @@ public IntervalShortMap(int capacity) { /** * Constructor with an initial interval map. *

- * The keys array must be in the same format returned by - * {@link #getIntervals() }. + * The keys array must be in the same format returned by {@link #getIntervals() }. * * @param keys initial keys content * @param vals initial values content @@ -63,6 +62,15 @@ public IntervalShortMap(double[] keys, short[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public IntervalShortMap(IntervalShortMap source) { + this(source.array, source.values); + } + /** * Get the value for the given interval. * @@ -98,8 +106,8 @@ public short getShort(Interval interval, short defaultValue) { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/IntervalStringMap.java b/src/main/java/org/gephi/graph/api/types/IntervalStringMap.java similarity index 88% rename from store/src/main/java/org/gephi/graph/api/types/IntervalStringMap.java rename to src/main/java/org/gephi/graph/api/types/IntervalStringMap.java index cbbdd84a..3e03399e 100644 --- a/store/src/main/java/org/gephi/graph/api/types/IntervalStringMap.java +++ b/src/main/java/org/gephi/graph/api/types/IntervalStringMap.java @@ -37,8 +37,8 @@ public IntervalStringMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -50,8 +50,7 @@ public IntervalStringMap(int capacity) { /** * Constructor with an initial interval map. *

- * The keys array must be in the same format returned by - * {@link #getIntervals() }. + * The keys array must be in the same format returned by {@link #getIntervals() }. * * @param keys initial keys content * @param vals initial values content @@ -62,6 +61,15 @@ public IntervalStringMap(double[] keys, String[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public IntervalStringMap(IntervalStringMap source) { + this(source.array, source.values); + } + @Override public Class getTypeClass() { return String.class; diff --git a/store/src/main/java/org/gephi/graph/api/types/TimeMap.java b/src/main/java/org/gephi/graph/api/types/TimeMap.java similarity index 85% rename from store/src/main/java/org/gephi/graph/api/types/TimeMap.java rename to src/main/java/org/gephi/graph/api/types/TimeMap.java index eecf07f4..97942b52 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimeMap.java +++ b/src/main/java/org/gephi/graph/api/types/TimeMap.java @@ -15,14 +15,18 @@ */ package org.gephi.graph.api.types; +import java.time.ZoneId; import org.gephi.graph.api.Estimator; import org.gephi.graph.api.Interval; import org.gephi.graph.api.TimeFormat; -import org.joda.time.DateTimeZone; /** - * Interface that defines the functionalities both timestamp and interval map - * have. + * Interface that defines the functionalities both timestamp and interval map have. + *

+ * Once a map is set on an element's dynamic column, the graph store maintains a time index over its keys. Calling + * {@link #put} or {@link #remove} on that instance bypasses the index and leaves it stale. Populate a map before + * setting it on an element, and go through the element's setAttribute and removeAttribute + * methods afterwards. * * @param key type * @param value type @@ -49,8 +53,8 @@ public interface TimeMap { /** * Get the estimated value for the given interval. *

- * The estimator is used to determine the way multiple interval values are - * merged together (e.g average, first, median). + * The estimator is used to determine the way multiple interval values are merged together (e.g average, first, + * median). * * @param interval interval query * @param estimator estimator used @@ -137,8 +141,8 @@ public interface TimeMap { * Returns this map as a string. * * @param timeFormat time format - * @param timeZone time zone + * @param zoneId time zone * @return map as string */ - public String toString(TimeFormat timeFormat, DateTimeZone timeZone); + public String toString(TimeFormat timeFormat, ZoneId zoneId); } diff --git a/store/src/main/java/org/gephi/graph/api/types/TimeSet.java b/src/main/java/org/gephi/graph/api/types/TimeSet.java similarity index 64% rename from store/src/main/java/org/gephi/graph/api/types/TimeSet.java rename to src/main/java/org/gephi/graph/api/types/TimeSet.java index 43469aea..5ffa27e4 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimeSet.java +++ b/src/main/java/org/gephi/graph/api/types/TimeSet.java @@ -15,12 +15,16 @@ */ package org.gephi.graph.api.types; +import java.time.ZoneId; import org.gephi.graph.api.TimeFormat; -import org.joda.time.DateTimeZone; /** - * Interface that defines the functionalities both timestamp and interval set - * have. + * Interface that defines the functionalities both timestamp and interval set have. + *

+ * Once a set is attached to an element, the graph store maintains a time index over its keys. Calling {@link #add} or + * {@link #remove} on that instance bypasses the index and leaves it stale. Populate a set before setting it on an + * element, and go through the element's addTimestamp/addInterval and + * removeTimestamp/removeInterval methods afterwards. * * @param key type */ @@ -64,19 +68,47 @@ public interface TimeSet { */ public boolean contains(K key); + /** + * Returns the minimum key in the set + * + * @return minimum key, or null if the set is empty. + */ + public K getMin(); + + /** + * Returns the maximum key in the set + * + * @return maximum key, or null if the set is empty. + */ + public K getMax(); + + /** + * Returns the minimum timestamp in the set + * + * @return minimum timestamp, or null if the set is empty. + */ + public Double getMinDouble(); + + /** + * Returns the maximum timestamp in the set + * + * @return maximum timestamp, or null if the set is empty. + */ + public Double getMaxDouble(); + /** * Returns an array of all keys in this set. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all keys */ public K[] toArray(); /** - * Returns the same result as {@link #toArray() } but in a primitive array if - * the underlying storage is in a primtive form. + * Returns the same result as {@link #toArray() } but in a primitive array if the underlying storage is in a + * primitive form. * * @return array of all keys */ @@ -102,5 +134,5 @@ public interface TimeSet { * @param timeZone time zone * @return set as string */ - public String toString(TimeFormat timeFormat, DateTimeZone timeZone); + public String toString(TimeFormat timeFormat, ZoneId timeZone); } diff --git a/store/src/main/java/org/gephi/graph/api/types/TimestampBooleanMap.java b/src/main/java/org/gephi/graph/api/types/TimestampBooleanMap.java similarity index 93% rename from store/src/main/java/org/gephi/graph/api/types/TimestampBooleanMap.java rename to src/main/java/org/gephi/graph/api/types/TimestampBooleanMap.java index f9555766..4162e426 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimestampBooleanMap.java +++ b/src/main/java/org/gephi/graph/api/types/TimestampBooleanMap.java @@ -39,8 +39,8 @@ public TimestampBooleanMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -63,6 +63,15 @@ public TimestampBooleanMap(double[] keys, boolean[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public TimestampBooleanMap(TimestampBooleanMap source) { + this(source.array, source.values); + } + /** * Get the value for the given timestamp. * @@ -157,8 +166,8 @@ public Class getTypeClass() { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/TimestampByteMap.java b/src/main/java/org/gephi/graph/api/types/TimestampByteMap.java similarity index 92% rename from store/src/main/java/org/gephi/graph/api/types/TimestampByteMap.java rename to src/main/java/org/gephi/graph/api/types/TimestampByteMap.java index d0f41008..de11da08 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimestampByteMap.java +++ b/src/main/java/org/gephi/graph/api/types/TimestampByteMap.java @@ -38,8 +38,8 @@ public TimestampByteMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -62,6 +62,15 @@ public TimestampByteMap(double[] keys, byte[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public TimestampByteMap(TimestampByteMap source) { + this(source.array, source.values); + } + /** * Get the value for the given timestamp. * @@ -120,8 +129,8 @@ public Class getTypeClass() { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/TimestampCharMap.java b/src/main/java/org/gephi/graph/api/types/TimestampCharMap.java similarity index 93% rename from store/src/main/java/org/gephi/graph/api/types/TimestampCharMap.java rename to src/main/java/org/gephi/graph/api/types/TimestampCharMap.java index ce3caa53..23685cfd 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimestampCharMap.java +++ b/src/main/java/org/gephi/graph/api/types/TimestampCharMap.java @@ -39,8 +39,8 @@ public TimestampCharMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -63,6 +63,15 @@ public TimestampCharMap(double[] keys, char[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public TimestampCharMap(TimestampCharMap source) { + this(source.array, source.values); + } + /** * Get the value for the given timestamp. * @@ -153,8 +162,8 @@ public Class getTypeClass() { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/TimestampDoubleMap.java b/src/main/java/org/gephi/graph/api/types/TimestampDoubleMap.java similarity index 91% rename from store/src/main/java/org/gephi/graph/api/types/TimestampDoubleMap.java rename to src/main/java/org/gephi/graph/api/types/TimestampDoubleMap.java index f6e4f1f5..491cab98 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimestampDoubleMap.java +++ b/src/main/java/org/gephi/graph/api/types/TimestampDoubleMap.java @@ -37,8 +37,8 @@ public TimestampDoubleMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -61,6 +61,15 @@ public TimestampDoubleMap(double[] keys, double[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public TimestampDoubleMap(TimestampDoubleMap source) { + this(source.array, source.values); + } + /** * Get the value for the given timestamp index. * @@ -101,8 +110,8 @@ public Class getTypeClass() { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/TimestampFloatMap.java b/src/main/java/org/gephi/graph/api/types/TimestampFloatMap.java similarity index 92% rename from store/src/main/java/org/gephi/graph/api/types/TimestampFloatMap.java rename to src/main/java/org/gephi/graph/api/types/TimestampFloatMap.java index a5215e11..c0522f3b 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimestampFloatMap.java +++ b/src/main/java/org/gephi/graph/api/types/TimestampFloatMap.java @@ -15,8 +15,8 @@ */ package org.gephi.graph.api.types; -import org.gephi.graph.api.Estimator; import java.math.BigDecimal; +import org.gephi.graph.api.Estimator; import org.gephi.graph.api.Interval; /** @@ -39,8 +39,8 @@ public TimestampFloatMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -63,6 +63,15 @@ public TimestampFloatMap(double[] keys, float[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public TimestampFloatMap(TimestampFloatMap source) { + this(source.array, source.values); + } + /** * Get the value for the given timestamp. * @@ -121,8 +130,8 @@ public Class getTypeClass() { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/TimestampIntegerMap.java b/src/main/java/org/gephi/graph/api/types/TimestampIntegerMap.java similarity index 92% rename from store/src/main/java/org/gephi/graph/api/types/TimestampIntegerMap.java rename to src/main/java/org/gephi/graph/api/types/TimestampIntegerMap.java index dbf90b45..404cb2ca 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimestampIntegerMap.java +++ b/src/main/java/org/gephi/graph/api/types/TimestampIntegerMap.java @@ -16,7 +16,6 @@ package org.gephi.graph.api.types; import org.gephi.graph.api.Estimator; -import java.math.BigDecimal; import org.gephi.graph.api.Interval; /** @@ -39,8 +38,8 @@ public TimestampIntegerMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -63,6 +62,15 @@ public TimestampIntegerMap(double[] keys, int[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public TimestampIntegerMap(TimestampIntegerMap source) { + this(source.array, source.values); + } + /** * Get the value for the given timestamp. * @@ -115,8 +123,8 @@ public Class getTypeClass() { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/TimestampLongMap.java b/src/main/java/org/gephi/graph/api/types/TimestampLongMap.java similarity index 92% rename from store/src/main/java/org/gephi/graph/api/types/TimestampLongMap.java rename to src/main/java/org/gephi/graph/api/types/TimestampLongMap.java index 660321e9..1f3905fd 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimestampLongMap.java +++ b/src/main/java/org/gephi/graph/api/types/TimestampLongMap.java @@ -16,7 +16,6 @@ package org.gephi.graph.api.types; import org.gephi.graph.api.Estimator; -import java.math.BigDecimal; import org.gephi.graph.api.Interval; /** @@ -39,8 +38,8 @@ public TimestampLongMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -63,6 +62,15 @@ public TimestampLongMap(double[] keys, long[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public TimestampLongMap(TimestampLongMap source) { + this(source.array, source.values); + } + /** * Get the value for the given timestamp. * @@ -115,8 +123,8 @@ public Class getTypeClass() { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/TimestampMap.java b/src/main/java/org/gephi/graph/api/types/TimestampMap.java similarity index 96% rename from store/src/main/java/org/gephi/graph/api/types/TimestampMap.java rename to src/main/java/org/gephi/graph/api/types/TimestampMap.java index f3f1b0f5..edbb5c98 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimestampMap.java +++ b/src/main/java/org/gephi/graph/api/types/TimestampMap.java @@ -16,22 +16,21 @@ package org.gephi.graph.api.types; import java.lang.reflect.Array; -import org.gephi.graph.api.Estimator; import java.math.BigDecimal; import java.math.RoundingMode; +import java.time.ZoneId; import java.util.Arrays; import org.gephi.graph.api.AttributeUtils; +import org.gephi.graph.api.Estimator; import org.gephi.graph.api.Interval; import org.gephi.graph.api.TimeFormat; import org.gephi.graph.impl.FormattingAndParsingUtils; -import org.joda.time.DateTimeZone; /** - * Abstract class that implement a sorted map between timestamp and attribute - * values. + * Abstract class that implement a sorted map between timestamp and attribute values. *

- * Implementations which extend this class customize the map for a unique type, - * which is represented by the T parameter. + * Implementations which extend this class customize the map for a unique type, which is represented by the + * T parameter. * * @param Value type */ @@ -52,8 +51,8 @@ public TimestampMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -267,8 +266,8 @@ public boolean contains(Double timestamp) { /** * Returns an array of all timestamps in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all timestamps */ @@ -331,7 +330,8 @@ public boolean equals(Object obj) { } Object o1 = this.getValue(i); Object o2 = other.getValue(i); - if ((o1 == null && o2 != null) || (o1 != null && o2 == null) || (o1 != null && o2 != null && !o1.equals(o2))) { + if ((o1 == null && o2 != null) || (o1 != null && o2 == null) || (o1 != null && o2 != null && !o1 + .equals(o2))) { return false; } } @@ -454,7 +454,7 @@ protected Double getAverageDouble(final Interval interval) { } @Override - public String toString(TimeFormat timeFormat, DateTimeZone timeZone) { + public String toString(TimeFormat timeFormat, ZoneId zoneId) { if (size == 0) { return FormattingAndParsingUtils.EMPTY_VALUE; } @@ -465,11 +465,12 @@ public String toString(TimeFormat timeFormat, DateTimeZone timeZone) { sb.append('<'); for (int i = 0; i < size; i++) { sb.append('['); - sb.append(AttributeUtils.printTimestampInFormat(array[i], timeFormat, timeZone)); + sb.append(AttributeUtils.printTimestampInFormat(array[i], timeFormat, zoneId)); sb.append(", "); String stringValue = values[i].toString(); - if (FormattingAndParsingUtils.containsDynamicSpecialCharacters(stringValue) || stringValue.trim().isEmpty()) { + if (FormattingAndParsingUtils.containsDynamicSpecialCharacters(stringValue) || stringValue.trim() + .isEmpty()) { sb.append('"'); sb.append(stringValue.replace("\\", "\\\\").replace("\"", "\\\"")); sb.append('"'); diff --git a/store/src/main/java/org/gephi/graph/api/types/TimestampSet.java b/src/main/java/org/gephi/graph/api/types/TimestampSet.java similarity index 88% rename from store/src/main/java/org/gephi/graph/api/types/TimestampSet.java rename to src/main/java/org/gephi/graph/api/types/TimestampSet.java index 1c0074b4..281cf283 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimestampSet.java +++ b/src/main/java/org/gephi/graph/api/types/TimestampSet.java @@ -15,11 +15,11 @@ */ package org.gephi.graph.api.types; +import java.time.ZoneId; import java.util.Arrays; import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.TimeFormat; import org.gephi.graph.impl.FormattingAndParsingUtils; -import org.joda.time.DateTimeZone; /** * Sorted set for timestamps. @@ -41,8 +41,8 @@ public TimestampSet() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -64,6 +64,15 @@ public TimestampSet(double[] arr) { size = arr.length; } + /** + * Copy constructor. + * + * @param source the set to copy + */ + public TimestampSet(TimestampSet source) { + this(source.array); + } + @Override public boolean add(Double timestamp) { return addInner(timestamp) >= 0; @@ -84,6 +93,32 @@ public boolean isEmpty() { return size == 0; } + @Override + public Double getMax() { + if (size > 0) { + return array[array.length - 1]; + } + return null; + } + + @Override + public Double getMin() { + if (size > 0) { + return array[0]; + } + return null; + } + + @Override + public Double getMaxDouble() { + return getMax(); + } + + @Override + public Double getMinDouble() { + return getMin(); + } + @Override public boolean contains(Double timestamp) { int index = Arrays.binarySearch(array, timestamp); @@ -191,7 +226,7 @@ public boolean equals(Object obj) { } @Override - public String toString(TimeFormat timeFormat, DateTimeZone timeZone) { + public String toString(TimeFormat timeFormat, ZoneId timeZone) { if (size == 0) { return FormattingAndParsingUtils.EMPTY_VALUE; } diff --git a/store/src/main/java/org/gephi/graph/api/types/TimestampShortMap.java b/src/main/java/org/gephi/graph/api/types/TimestampShortMap.java similarity index 92% rename from store/src/main/java/org/gephi/graph/api/types/TimestampShortMap.java rename to src/main/java/org/gephi/graph/api/types/TimestampShortMap.java index 94a77295..0c5b3a2e 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimestampShortMap.java +++ b/src/main/java/org/gephi/graph/api/types/TimestampShortMap.java @@ -16,7 +16,6 @@ package org.gephi.graph.api.types; import org.gephi.graph.api.Estimator; -import java.math.BigDecimal; import org.gephi.graph.api.Interval; /** @@ -39,8 +38,8 @@ public TimestampShortMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -63,6 +62,15 @@ public TimestampShortMap(double[] keys, short[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public TimestampShortMap(TimestampShortMap source) { + this(source.array, source.values); + } + /** * Get the value for the given timestamp. * @@ -115,8 +123,8 @@ public Class getTypeClass() { /** * Returns an array of all values in this map. *

- * This method may return a reference to the underlying array so clients - * should make a copy if the array is written to. + * This method may return a reference to the underlying array so clients should make a copy if the array is written + * to. * * @return array of all values */ diff --git a/store/src/main/java/org/gephi/graph/api/types/TimestampStringMap.java b/src/main/java/org/gephi/graph/api/types/TimestampStringMap.java similarity index 89% rename from store/src/main/java/org/gephi/graph/api/types/TimestampStringMap.java rename to src/main/java/org/gephi/graph/api/types/TimestampStringMap.java index 28bbfdb1..26988049 100644 --- a/store/src/main/java/org/gephi/graph/api/types/TimestampStringMap.java +++ b/src/main/java/org/gephi/graph/api/types/TimestampStringMap.java @@ -37,8 +37,8 @@ public TimestampStringMap() { /** * Constructor with capacity. *

- * Using this constructor can improve performances if the number of - * timestamps is known in advance as it minimizes array resizes. + * Using this constructor can improve performances if the number of timestamps is known in advance as it minimizes + * array resizes. * * @param capacity timestamp capacity */ @@ -61,6 +61,15 @@ public TimestampStringMap(double[] keys, String[] vals) { System.arraycopy(vals, 0, values, 0, vals.length); } + /** + * Copy constructor. + * + * @param source the map to copy + */ + public TimestampStringMap(TimestampStringMap source) { + this(source.array, source.values); + } + @Override public Class getTypeClass() { return String.class; diff --git a/src/main/java/org/gephi/graph/api/types/package.html b/src/main/java/org/gephi/graph/api/types/package.html new file mode 100644 index 00000000..0a437254 --- /dev/null +++ b/src/main/java/org/gephi/graph/api/types/package.html @@ -0,0 +1,4 @@ + + + Custom types the API supports, in addition of primitive and arrays. + diff --git a/store/src/main/java/org/gephi/graph/impl/ArraysParser.java b/src/main/java/org/gephi/graph/impl/ArraysParser.java similarity index 93% rename from store/src/main/java/org/gephi/graph/impl/ArraysParser.java rename to src/main/java/org/gephi/graph/impl/ArraysParser.java index 03d69d1b..bfe1e8ec 100644 --- a/store/src/main/java/org/gephi/graph/impl/ArraysParser.java +++ b/src/main/java/org/gephi/graph/impl/ArraysParser.java @@ -15,17 +15,16 @@ */ package org.gephi.graph.impl; +import static org.gephi.graph.impl.FormattingAndParsingUtils.COMMA; +import static org.gephi.graph.impl.FormattingAndParsingUtils.EMPTY_VALUE; +import static org.gephi.graph.impl.FormattingAndParsingUtils.LEFT_BOUND_SQUARE_BRACKET; +import static org.gephi.graph.impl.FormattingAndParsingUtils.RIGHT_BOUND_SQUARE_BRACKET; + import java.io.IOException; import java.io.StringReader; import java.lang.reflect.Array; import java.util.ArrayList; import org.gephi.graph.api.AttributeUtils; -import static org.gephi.graph.impl.FormattingAndParsingUtils.COMMA; -import static org.gephi.graph.impl.FormattingAndParsingUtils.LEFT_BOUND_BRACKET; -import static org.gephi.graph.impl.FormattingAndParsingUtils.LEFT_BOUND_SQUARE_BRACKET; -import static org.gephi.graph.impl.FormattingAndParsingUtils.RIGHT_BOUND_BRACKET; -import static org.gephi.graph.impl.FormattingAndParsingUtils.RIGHT_BOUND_SQUARE_BRACKET; -import static org.gephi.graph.impl.FormattingAndParsingUtils.EMPTY_VALUE; /** *

@@ -82,8 +81,6 @@ public static T[] parseArray(Class arrayTypeClass, String input) throws c = (char) r; switch (c) { case RIGHT_BOUND_SQUARE_BRACKET: - case RIGHT_BOUND_BRACKET: - case LEFT_BOUND_BRACKET: case LEFT_BOUND_SQUARE_BRACKET: case ' ': case '\t': @@ -100,7 +97,7 @@ public static T[] parseArray(Class arrayTypeClass, String input) throws default: reader.skip(-1);// Go backwards 1 position, for reading // start of value - String value = FormattingAndParsingUtils.parseValue(reader); + String value = FormattingAndParsingUtils.parseValue(reader, false); if (value.equals("null")) { value = null;// Special null value only when not in // literal parsing mode @@ -128,13 +125,11 @@ public static T[] parseArray(Class arrayTypeClass, String input) throws /** * Parses an array of any primitive type. * - * @param Primitive type wrapper. For example Integer for int array or - * Long for long array. + * @param Primitive type wrapper. For example Integer for int array or Long for long array. * @param arrayTypeClass Array type to parse * @param input Input string to parse * @return Parsed array - * @throws IllegalArgumentException Parsing exception, or if any of the - * parsed array values is null + * @throws IllegalArgumentException Parsing exception, or if any of the parsed array values is null */ public static Object parseArrayAsPrimitiveArray(Class arrayTypeClass, String input) throws IllegalArgumentException { T[] array = parseArray(arrayTypeClass, input); diff --git a/src/main/java/org/gephi/graph/impl/AttributesImpl.java b/src/main/java/org/gephi/graph/impl/AttributesImpl.java new file mode 100644 index 00000000..ab7a8eeb --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/AttributesImpl.java @@ -0,0 +1,267 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import org.gephi.graph.api.Column; +import org.gephi.graph.api.Estimator; +import org.gephi.graph.api.Interval; +import org.gephi.graph.api.types.IntervalMap; +import org.gephi.graph.api.types.IntervalSet; +import org.gephi.graph.api.types.TimeMap; +import org.gephi.graph.api.types.TimeSet; +import org.gephi.graph.api.types.TimestampMap; +import org.gephi.graph.api.types.TimestampSet; + +public class AttributesImpl { + + // Attributes + protected Object[] attributes; + + public AttributesImpl(ColumnStore columnStore) { + if (columnStore != null) { + int length = columnStore.length; + final ColumnImpl[] cols = columnStore.columns; + attributes = new Object[length]; + for (int i = 0; i < length; i++) { + Column c = cols[i]; + if (c != null && !c.isProperty()) { + attributes[i] = c.getDefaultValue(); + } + } + } else { + attributes = new Object[GraphStoreConfiguration.EDGE_DEFAULT_COLUMNS]; + } + } + + public Object getId() { + return attributes[GraphStoreConfiguration.ELEMENT_ID_INDEX]; + } + + public void setId(Object id) { + attributes[GraphStoreConfiguration.ELEMENT_ID_INDEX] = id; + } + + public String getLabel() { + if (GraphStoreConfiguration.ENABLE_ELEMENT_LABEL && attributes.length > GraphStoreConfiguration.ELEMENT_LABEL_INDEX) { + return (String) attributes[GraphStoreConfiguration.ELEMENT_LABEL_INDEX]; + } + return null; + } + + public Object getAttribute(Column column) { + return getAttribute(column.getIndex()); + } + + public Object getAttribute(int index) { + Object res = null; + synchronized (this) { + if (index < attributes.length) { + res = attributes[index]; + } + } + + return res; + } + + protected Object getAttribute(Column column, Object timeObject, Estimator estimator) { + int index = column.getIndex(); + synchronized (this) { + Object dynamicValue = null; + if (index < attributes.length) { + dynamicValue = attributes[index]; + } + if (TimeSet.class.isAssignableFrom(column.getTypeClass())) { + return dynamicValue; + } else { + TimeMap timeMap = (TimeMap) dynamicValue; + if (timeMap != null && !timeMap.isEmpty()) { + if (estimator == null) { + return timeMap.get(timeObject, column.getDefaultValue()); + } else { + return timeMap.get((Interval) timeObject, estimator); + } + } + } + } + return null; + } + + protected Object removeTimeAttribute(Column column, Object timeObject) { + int index = column.getIndex(); + Object oldValue = null; + boolean res = false; + synchronized (this) { + TimeMap dynamicValue = (TimeMap) attributes[index]; + if (dynamicValue != null) { + oldValue = dynamicValue.get(timeObject, null); + + res = dynamicValue.remove(timeObject); + } + } + return oldValue; + } + + protected Object setAttribute(Column column, Object value) { + int index = column.getIndex(); + return setAttribute(index, value); + } + + public Object setAttribute(int index, Object value) { + Object oldValue = null; + synchronized (this) { + if (index >= attributes.length) { + Object[] newArray = new Object[index + 1]; + System.arraycopy(attributes, 0, newArray, 0, attributes.length); + attributes = newArray; + } else { + oldValue = attributes[index]; + } + attributes[index] = value; + } + return oldValue; + } + + private Object ensureSize(int index) { + if (index >= attributes.length) { + Object[] newArray = new Object[index + 1]; + System.arraycopy(attributes, 0, newArray, 0, attributes.length); + attributes = newArray; + } else { + return attributes[index]; + } + return null; + } + + /** + * Puts a value at the given time in the column's map, creating the map if needed. + * + * @return true if the time was not already in the map + */ + protected boolean setAttribute(Column column, Object value, Object timeObject) { + int index = column.getIndex(); + Object oldValue = null; + synchronized (this) { + oldValue = ensureSize(index); + TimeMap dynamicValue; + if (oldValue == null) { + try { + attributes[index] = dynamicValue = (TimeMap) column.getTypeClass().getDeclaredConstructor() + .newInstance(); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException + | InvocationTargetException ex) { + throw new RuntimeException(ex); + } + } else { + dynamicValue = (TimeMap) oldValue; + } + + return dynamicValue.put(timeObject, value); + } + } + + protected boolean addTime(Object timeObject) { + boolean res; + synchronized (this) { + TimeSet timeSet = getTimeSet(); + if (timeSet == null) { + if (timeObject instanceof Interval) { + timeSet = new IntervalSet(); + } else { + timeSet = new TimestampSet(); + } + int index = GraphStoreConfiguration.ELEMENT_TIMESET_INDEX; + if (index >= attributes.length) { + Object[] newArray = new Object[index + 1]; + System.arraycopy(attributes, 0, newArray, 0, attributes.length); + attributes = newArray; + } + attributes[index] = timeSet; + } + res = timeSet.add(timeObject); + } + return res; + } + + protected boolean removeTime(Object timeObject) { + boolean res = false; + synchronized (this) { + TimeSet timeSet = getTimeSet(); + if (timeSet != null) { + res = timeSet.remove(timeObject); + } + } + + return res; + } + + protected TimeSet getTimeSet() { + if (GraphStoreConfiguration.ENABLE_ELEMENT_TIME_SET && GraphStoreConfiguration.ELEMENT_TIMESET_INDEX < attributes.length) { + return (TimeSet) attributes[GraphStoreConfiguration.ELEMENT_TIMESET_INDEX]; + } + return null; + } + + protected boolean hasTime(Object timeObject) { + synchronized (this) { + TimeSet timeSet = getTimeSet(); + if (timeSet != null) { + return timeSet.contains(timeObject); + } + } + return false; + } + + protected Iterable getAttributes(Column column) { + int index = column.getIndex(); + TimeMap dynamicValue = null; + synchronized (this) { + if (index < attributes.length) { + dynamicValue = (TimeMap) attributes[index]; + } + if (dynamicValue != null) { + Object[] values = dynamicValue.toValuesArray(); + if (dynamicValue instanceof TimestampMap) { + return new TimeAttributeIterable(((TimestampMap) dynamicValue).getTimestamps(), values); + } else if (dynamicValue instanceof IntervalMap) { + return new TimeAttributeIterable(((IntervalMap) dynamicValue).toKeysArray(), values); + } + } + + } + return TimeAttributeIterable.EMPTY_ITERABLE; + } + + protected Object getTimeSetArray() { + synchronized (this) { + TimeSet timeSet = getTimeSet(); + if (timeSet != null) { + return timeSet.toPrimitiveArray(); + } + } + return null; + } + + public Object[] getBackingArray() { + return attributes; + } + + // Used by serialization + protected void setBackingArray(Object[] attributes) { + this.attributes = attributes; + } +} diff --git a/store/src/main/java/org/gephi/graph/impl/ColumnImpl.java b/src/main/java/org/gephi/graph/impl/ColumnImpl.java similarity index 93% rename from store/src/main/java/org/gephi/graph/impl/ColumnImpl.java rename to src/main/java/org/gephi/graph/impl/ColumnImpl.java index 748e472d..7c11b3ed 100644 --- a/store/src/main/java/org/gephi/graph/impl/ColumnImpl.java +++ b/src/main/java/org/gephi/graph/impl/ColumnImpl.java @@ -19,9 +19,9 @@ import java.util.List; import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Column; +import org.gephi.graph.api.Estimator; import org.gephi.graph.api.Origin; import org.gephi.graph.api.Table; -import org.gephi.graph.api.Estimator; import org.gephi.graph.api.types.TimeMap; import org.gephi.graph.api.types.TimeSet; @@ -67,7 +67,7 @@ public ColumnImpl(TableImpl table, String id, Class typeClass, String title, Obj this.indexed = indexed; this.readOnly = readOnly; this.dynamic = TimeMap.class.isAssignableFrom(typeClass) || TimeSet.class.isAssignableFrom(typeClass); - this.observers = GraphStoreConfiguration.ENABLE_OBSERVERS ? new ArrayList<>() : null; + this.observers = table != null && table.configuration.isEnableObservers() ? new ArrayList<>() : null; this.estimator = this.dynamic ? Estimator.FIRST : null; } @@ -125,6 +125,11 @@ public boolean isDynamic() { return dynamic; } + @Override + public boolean isDynamicAttribute() { + return dynamic && TimeMap.class.isAssignableFrom(typeClass); + } + @Override public boolean isReadOnly() { return readOnly; @@ -148,6 +153,11 @@ public void setStoreId(int storeId) { this.storeId = storeId; } + @Override + public boolean exists() { + return storeId != ColumnStore.NULL_ID; + } + @Override public String toString() { return title + " (" + typeClass.toString() + ")"; @@ -184,14 +194,14 @@ public ColumnObserverImpl createColumnObserver(boolean withDiff) { synchronized (observers) { observers.add(observer); } - return observer; + } else { + throw new UnsupportedOperationException("Observers are disabled. Enable them in Configuration"); } - return null; } protected void destroyColumnObserver(ColumnObserverImpl observer) { - if (observers != null) { + if (observers != null && !observers.isEmpty()) { synchronized (observers) { observers.remove(observer); } diff --git a/src/main/java/org/gephi/graph/impl/ColumnIndexImpl.java b/src/main/java/org/gephi/graph/impl/ColumnIndexImpl.java new file mode 100644 index 00000000..d01816db --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/ColumnIndexImpl.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import org.gephi.graph.api.ColumnIndex; +import org.gephi.graph.api.Element; + +public interface ColumnIndexImpl extends ColumnIndex { + + void destroy(); + + void clear(); + + K putValue(T element, K value); + + void removeValue(T element, K value); + + K replaceValue(T element, K oldValue, K newValue); +} diff --git a/src/main/java/org/gephi/graph/impl/ColumnNoIndexImpl.java b/src/main/java/org/gephi/graph/impl/ColumnNoIndexImpl.java new file mode 100644 index 00000000..14ca8c44 --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/ColumnNoIndexImpl.java @@ -0,0 +1,300 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.gephi.graph.impl; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import org.gephi.graph.api.AttributeUtils; +import org.gephi.graph.api.Column; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Element; +import org.gephi.graph.api.Estimator; +import org.gephi.graph.api.Graph; +import org.gephi.graph.api.GraphLock; +import org.gephi.graph.api.Node; + +public class ColumnNoIndexImpl implements ColumnIndexImpl { + + // Data + protected final ColumnImpl column; + // Stores + protected final Class elementClass; + // Graph + protected final Graph graph; + protected final GraphLock graphLock; + // Version + protected final AtomicInteger version = new AtomicInteger(Integer.MIN_VALUE); + + protected ColumnNoIndexImpl(ColumnImpl column, Graph graph, Class elementClass) { + this.column = column; + this.elementClass = elementClass; + this.graph = graph; + this.graphLock = graph != null ? graph.getLock() : null; + } + + private Iterator getElementIterator() { + if (elementClass.equals(Node.class)) { + return (Iterator) graph.getNodes().iterator(); + } else if (elementClass.equals(Edge.class)) { + return (Iterator) graph.getEdges().iterator(); + } + return null; + } + + @Override + public int count(K value) { + lock(); + try { + Iterator elementIterator = getElementIterator(); + int count = 0; + if (elementIterator != null) { + while (elementIterator.hasNext()) { + ElementImpl element = (ElementImpl) elementIterator.next(); + K obj = (K) element.getAttribute(column, graph.getView()); + if (value == null && obj == null) { + count++; + } else if (value != null && value.equals(obj)) { + count++; + } + } + } + return count; + } finally { + unlock(); + } + } + + @Override + public Iterable get(K value) { + return new ElementWithValueIterable(getElementIterator(), value); + } + + @Override + public Collection values() { + lock(); + try { + Iterator elementIterator = getElementIterator(); + Set set = new ObjectOpenHashSet<>(); + if (elementIterator != null) { + while (elementIterator.hasNext()) { + ElementImpl element = (ElementImpl) elementIterator.next(); + K obj = (K) element.getAttribute(column, graph.getView()); + set.add(obj); + } + } + return set; + } finally { + unlock(); + } + } + + @Override + public int countValues() { + return values().size(); + } + + @Override + public int countElements() { + if (elementClass.equals(Node.class)) { + return graph.getNodeCount(); + } else if (elementClass.equals(Edge.class)) { + return graph.getEdgeCount(); + } + return 0; + } + + @Override + public boolean isSortable() { + return AttributeUtils.isNumberType(column.getTypeClass()) && !AttributeUtils.isArrayType(column.getTypeClass()); + } + + @Override + public Number getMinValue() { + if (!isSortable()) { + throw new UnsupportedOperationException("Only supported for sortable columns"); + } + lock(); + try { + Number min = null; + Iterator elementIterator = getElementIterator(); + if (elementIterator != null) { + double minN = Double.POSITIVE_INFINITY; + while (elementIterator.hasNext()) { + ElementImpl element = (ElementImpl) elementIterator.next(); + Number num = (Number) element.getAttribute(column, graph.getView(), Estimator.MIN); + if (min == null || (num != null && num.doubleValue() < minN)) { + if (num != null) { + minN = num.doubleValue(); + } + min = num; + } + } + } + return min; + } finally { + unlock(); + } + } + + @Override + public Number getMaxValue() { + if (!isSortable()) { + throw new UnsupportedOperationException("Only supported for sortable columns"); + } + lock(); + try { + Number max = null; + Iterator elementIterator = getElementIterator(); + if (elementIterator != null) { + double maxN = Double.NEGATIVE_INFINITY; + + while (elementIterator.hasNext()) { + ElementImpl element = (ElementImpl) elementIterator.next(); + Number num = (Number) element.getAttribute(column, graph.getView(), Estimator.MAX); + if (max == null || (num != null && num.doubleValue() > maxN)) { + if (num != null) { + maxN = num.doubleValue(); + } + max = num; + } + } + } + return max; + } finally { + unlock(); + } + } + + @Override + public Column getColumn() { + return column; + } + + @Override + public int getVersion() { + return version.get(); + } + + @Override + public Iterator>> iterator() { + // TODO + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void clear() { + // Nothing to clear + version.incrementAndGet(); + } + + @Override + public void destroy() { + // Nothing to destroy + version.incrementAndGet(); + } + + @Override + public K putValue(T element, K value) { + version.incrementAndGet(); + return value; + } + + @Override + public K replaceValue(T element, K oldValue, K newValue) { + version.incrementAndGet(); + return newValue; + } + + @Override + public void removeValue(T element, K value) { + // Nothing to remove + version.incrementAndGet(); + } + + private class ElementWithValueIterable implements Iterable { + + private final Iterator ite; + private final K value; + + public ElementWithValueIterable(Iterator ite, K value) { + this.ite = ite; + this.value = value; + } + + @Override + public Iterator iterator() { + return new ElementWithValueIterator(ite, value); + } + } + + private class ElementWithValueIterator implements Iterator { + + private final Iterator itr; + private final K value; + private T pointer; + + public ElementWithValueIterator(Iterator itr, K value) { + this.itr = itr; + this.value = value; + lock(); + } + + @Override + public boolean hasNext() { + while (pointer == null && itr.hasNext()) { + T element = itr.next(); + K val = (K) element.getAttribute(column, graph.getView()); + if ((value == null && val == null) || (val != null && val.equals(value))) { + pointer = element; + } + } + if (pointer != null) { + return true; + } + unlock(); + return false; + } + + @Override + public T next() { + T res = pointer; + pointer = null; + return res; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Not supported."); + } + } + + private void lock() { + if (graphLock != null) { + graphLock.readLock(); + } + } + + private void unlock() { + if (graphLock != null) { + graphLock.readUnlock(); + } + } +} diff --git a/store/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java b/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java similarity index 84% rename from store/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java rename to src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java index 1cc40ef0..f6a38ef4 100644 --- a/store/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java +++ b/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java @@ -15,10 +15,10 @@ */ package org.gephi.graph.impl; -import cern.colt.bitvector.BitVector; -import cern.colt.bitvector.QuickBitVector; +import java.util.BitSet; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectList; +import it.unimi.dsi.fastutil.objects.ObjectLists; import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Column; import org.gephi.graph.api.ColumnDiff; @@ -39,7 +39,7 @@ public class ColumnObserverImpl implements ColumnObserver { protected boolean destroyed; // Config protected final boolean withDiff; - protected BitVector bitVector; + protected BitSet bitVector; // Cache protected ColumnDiffImpl columnDiff; @@ -108,7 +108,7 @@ private void refreshDiff() { boolean node = AttributeUtils.isNodeColumn(column); columnDiff = node ? new NodeColumnDiffImpl() : new EdgeColumnDiffImpl(); - int size = bitVector.size(); + int size = bitVector.length(); for (int i = 0; i < size; i++) { boolean t = bitVector.get(i); @@ -155,7 +155,8 @@ protected final class NodeColumnDiffImpl extends ColumnDiffImpl { @Override public NodeIterable getTouchedElements() { if (!touchedElements.isEmpty()) { - return graphStore.getNodeIterableWrapper(touchedElements.iterator(), false); + return new NodeIterableWrapper(() -> ObjectLists.unmodifiable(touchedElements).iterator(), + () -> ObjectLists.unmodifiable(touchedElements).spliterator(), null); } return NodeIterable.NodeIterableEmpty.EMPTY; @@ -167,8 +168,8 @@ protected final class EdgeColumnDiffImpl extends ColumnDiffImpl { @Override public EdgeIterable getTouchedElements() { if (!touchedElements.isEmpty()) { - return graphStore.getEdgeIterableWrapper(touchedElements.iterator(), false); - + return new EdgeIterableWrapper(() -> ObjectLists.unmodifiable(touchedElements).iterator(), + () -> ObjectLists.unmodifiable(touchedElements).spliterator(), null); } return EdgeIterable.EdgeIterableEmpty.EMPTY; } @@ -177,19 +178,11 @@ public EdgeIterable getTouchedElements() { private void ensureVectorSize(ElementImpl element) { int sid = element.getStoreId(); if (bitVector == null) { - bitVector = new BitVector(sid + 1); - } else if (sid >= bitVector.size()) { - int newSize = Math - .min(Math.max(sid + 1, (int) (sid * GraphStoreConfiguration.COLUMNDIFF_GROWING_FACTOR)), Integer.MAX_VALUE); - bitVector = growBitVector(bitVector, newSize); + int initialSize = Math.min(Math + .max(sid + 1, (int) (sid * GraphStoreConfiguration.COLUMNDIFF_GROWING_FACTOR)), Integer.MAX_VALUE); + bitVector = new BitSet(initialSize); } - } - - private BitVector growBitVector(BitVector bitVector, int size) { - long[] elements = bitVector.elements(); - long[] newElements = QuickBitVector.makeBitVector(size, 1); - System.arraycopy(elements, 0, newElements, 0, elements.length); - return new BitVector(newElements, size); + // BitSet grows automatically when setting bits, no need to manually grow } private void readLock() { diff --git a/src/main/java/org/gephi/graph/impl/ColumnStandardIndexImpl.java b/src/main/java/org/gephi/graph/impl/ColumnStandardIndexImpl.java new file mode 100644 index 00000000..16e047a3 --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/ColumnStandardIndexImpl.java @@ -0,0 +1,782 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.gephi.graph.impl; + +import it.unimi.dsi.fastutil.booleans.BooleanArrays; +import it.unimi.dsi.fastutil.bytes.Byte2ObjectAVLTreeMap; +import it.unimi.dsi.fastutil.bytes.ByteArrays; +import it.unimi.dsi.fastutil.chars.Char2ObjectAVLTreeMap; +import it.unimi.dsi.fastutil.chars.CharArrays; +import it.unimi.dsi.fastutil.doubles.Double2ObjectAVLTreeMap; +import it.unimi.dsi.fastutil.doubles.DoubleArrays; +import it.unimi.dsi.fastutil.floats.Float2ObjectAVLTreeMap; +import it.unimi.dsi.fastutil.floats.FloatArrays; +import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap; +import it.unimi.dsi.fastutil.ints.IntArrays; +import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap; +import it.unimi.dsi.fastutil.longs.LongArrays; +import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrays; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.shorts.Short2ObjectAVLTreeMap; +import it.unimi.dsi.fastutil.shorts.ShortArrays; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.concurrent.atomic.AtomicInteger; +import org.gephi.graph.api.Element; + +public abstract class ColumnStandardIndexImpl implements ColumnIndexImpl { + + // Lock (optional) + protected final TableLockImpl lock; + // Data + protected final ColumnImpl column; + protected final ValueSet nullSet; + protected Map> map; + // Variable + protected int elements; + // Version + protected final AtomicInteger version = new AtomicInteger(Integer.MIN_VALUE); + + protected ColumnStandardIndexImpl(ColumnImpl column) { + this.column = column; + this.nullSet = new ValueSet<>(null); + this.lock = column.table != null && column.table.configuration.isEnableAutoLocking() ? new TableLockImpl() + : null; + } + + protected static boolean isSupportedType(ColumnImpl col) { + return !col.isDynamicAttribute(); + } + + @Override + public K putValue(T element, K value) { + lock(); + try { + if (value == null) { + if (nullSet.add(element)) { + elements++; + version.incrementAndGet(); + } + } else { + ValueSet set = getValueSet(value); + if (set == null) { + set = addValue(value); + } + value = set.value; + + if (set.add(element)) { + elements++; + version.incrementAndGet(); + } + } + } finally { + unlock(); + } + return value; + } + + @Override + public void removeValue(T element, K value) { + lock(); + try { + if (value == null) { + if (nullSet.remove(element)) { + elements--; + version.incrementAndGet(); + } + } else { + ValueSet set = getValueSet(value); + if (set.remove(element)) { + elements--; + version.incrementAndGet(); + } + if (set.isEmpty()) { + removeValue(value); + } + } + } finally { + unlock(); + } + } + + @Override + public K replaceValue(T element, K oldValue, K newValue) { + removeValue(element, oldValue); + return putValue(element, newValue); + } + + protected int getCount(K value) { + lock(); + try { + if (value == null) { + return nullSet.size(); + } + ValueSet valueSet = getValueSet(value); + if (valueSet != null) { + return valueSet.size(); + } else { + return 0; + } + } finally { + unlock(); + } + } + + @Override + public int count(K value) { + return getCount(value); + } + + @Override + public Collection values() { + lock(); + try { + return new ArrayList<>(new WithNullDecorator()); + } finally { + unlock(); + } + } + + @Override + public int countValues() { + return (nullSet.isEmpty() ? 0 : 1) + map.size(); + } + + @Override + public int countElements() { + return elements; + } + + @Override + public Number getMinValue() { + lock(); + try { + if (isSortable()) { + if (map.isEmpty()) { + return null; + } else { + return (Number) ((SortedMap) map).firstKey(); + } + } else { + throw new UnsupportedOperationException("'" + column.getId() + "' is not a sortable column (" + column + .getTypeClass().getSimpleName() + ")."); + } + } finally { + unlock(); + } + } + + @Override + public Number getMaxValue() { + lock(); + try { + if (isSortable()) { + if (map.isEmpty()) { + return null; + } else { + return (Number) ((SortedMap) map).lastKey(); + } + } else { + throw new UnsupportedOperationException("'" + column.getId() + "' is not a sortable column (" + column + .getTypeClass().getSimpleName() + ")."); + } + } finally { + unlock(); + } + } + + @Override + public void destroy() { + lock(); + try { + map = null; + nullSet.clear(); + elements = 0; + version.incrementAndGet(); + } finally { + unlock(); + } + } + + @Override + public void clear() { + lock(); + try { + map.clear(); + nullSet.clear(); + elements = 0; + version.incrementAndGet(); + } finally { + unlock(); + } + } + + @Override + public Iterator>> iterator() { + return new EntryIterator(); + } + + @Override + public Iterable get(K value) { + lock(); + ValueSet valueSet = getValueSet(value); + if (valueSet == null) { + unlock(); + return ValueSet.EMPTY; + } + return new LockableIterable<>(valueSet.set); + } + + protected ValueSet getValueSet(K value) { + if (value == null) { + return nullSet; + } + return map.get(value); + } + + protected void removeValue(K value) { + map.remove(value); + } + + protected ValueSet addValue(K value) { + ValueSet valueSet = new ValueSet<>(value); + map.put(value, valueSet); + return valueSet; + } + + @Override + public boolean isSortable() { + return Number.class.isAssignableFrom(column.getTypeClass()) && map instanceof SortedMap; + } + + @Override + public ColumnImpl getColumn() { + return column; + } + + @Override + public int getVersion() { + return version.get(); + } + + void lock() { + if (lock != null) { + lock.lock(); + } + } + + void unlock() { + if (lock != null) { + lock.unlock(); + } + } + + protected static class DefaultStandardIndex extends ColumnStandardIndexImpl { + + public DefaultStandardIndex(ColumnImpl column) { + super(column); + + map = new Object2ObjectOpenHashMap<>(); + } + } + + protected static class BooleanStandardIndex extends ColumnStandardIndexImpl { + + public BooleanStandardIndex(ColumnImpl column) { + super(column); + + map = new Object2ObjectOpenHashMap<>(); + } + } + + protected static class DoubleStandardIndex extends ColumnStandardIndexImpl { + + public DoubleStandardIndex(ColumnImpl column) { + super(column); + + map = new Double2ObjectAVLTreeMap<>(); + } + } + + protected static class IntegerStandardIndex extends ColumnStandardIndexImpl { + + public IntegerStandardIndex(ColumnImpl column) { + super(column); + + map = new Int2ObjectAVLTreeMap<>(); + } + } + + protected static class FloatStandardIndex extends ColumnStandardIndexImpl { + + public FloatStandardIndex(ColumnImpl column) { + super(column); + + map = new Float2ObjectAVLTreeMap<>(); + } + } + + protected static class LongStandardIndex extends ColumnStandardIndexImpl { + + public LongStandardIndex(ColumnImpl column) { + super(column); + + map = new Long2ObjectAVLTreeMap<>(); + } + } + + protected static class ShortStandardIndex extends ColumnStandardIndexImpl { + + public ShortStandardIndex(ColumnImpl column) { + super(column); + + map = new Short2ObjectAVLTreeMap<>(); + } + } + + protected static class ByteStandardIndex extends ColumnStandardIndexImpl { + + public ByteStandardIndex(ColumnImpl column) { + super(column); + + map = new Byte2ObjectAVLTreeMap<>(); + } + } + + protected static class GenericNumberStandardIndex extends ColumnStandardIndexImpl { + + public GenericNumberStandardIndex(ColumnImpl column) { + super(column); + + map = new Object2ObjectAVLTreeMap<>(); + } + } + + protected static class CharStandardIndex extends ColumnStandardIndexImpl { + + public CharStandardIndex(ColumnImpl column) { + super(column); + + map = new Char2ObjectAVLTreeMap<>(); + } + } + + protected static class DefaultArrayStandardIndex extends ColumnStandardIndexImpl { + + public DefaultArrayStandardIndex(ColumnImpl column) { + super(column); + + map = new Object2ObjectOpenCustomHashMap<>(ObjectArrays.HASH_STRATEGY); + } + } + + protected static class BooleanArrayStandardIndex extends ColumnStandardIndexImpl { + + public BooleanArrayStandardIndex(ColumnImpl column) { + super(column); + + map = new Object2ObjectOpenCustomHashMap<>(BooleanArrays.HASH_STRATEGY); + } + } + + protected static class DoubleArrayStandardIndex extends ColumnStandardIndexImpl { + + public DoubleArrayStandardIndex(ColumnImpl column) { + super(column); + + map = new Object2ObjectOpenCustomHashMap<>(DoubleArrays.HASH_STRATEGY); + } + } + + protected static class IntegerArrayStandardIndex extends ColumnStandardIndexImpl { + + public IntegerArrayStandardIndex(ColumnImpl column) { + super(column); + + map = new Object2ObjectOpenCustomHashMap<>(IntArrays.HASH_STRATEGY); + } + } + + protected static class FloatArrayStandardIndex extends ColumnStandardIndexImpl { + + public FloatArrayStandardIndex(ColumnImpl column) { + super(column); + + map = new Object2ObjectOpenCustomHashMap<>(FloatArrays.HASH_STRATEGY); + } + } + + protected static class LongArrayStandardIndex extends ColumnStandardIndexImpl { + + public LongArrayStandardIndex(ColumnImpl column) { + super(column); + + map = new Object2ObjectOpenCustomHashMap<>(LongArrays.HASH_STRATEGY); + } + } + + protected static class ShortArrayStandardIndex extends ColumnStandardIndexImpl { + + public ShortArrayStandardIndex(ColumnImpl column) { + super(column); + + map = new Object2ObjectOpenCustomHashMap<>(ShortArrays.HASH_STRATEGY); + } + } + + protected static class ByteArrayStandardIndex extends ColumnStandardIndexImpl { + + public ByteArrayStandardIndex(ColumnImpl column) { + super(column); + + map = new Object2ObjectOpenCustomHashMap<>(ByteArrays.HASH_STRATEGY); + } + } + + protected static class CharArrayStandardIndex extends ColumnStandardIndexImpl { + + public CharArrayStandardIndex(ColumnImpl column) { + super(column); + + map = new Object2ObjectOpenCustomHashMap<>(CharArrays.HASH_STRATEGY); + } + } + + protected static final class ValueSet implements Set { + + protected static ValueSet EMPTY = new ValueSet(null); + protected final K value; + private final Set set; + + public ValueSet(K value) { + this.value = value; + this.set = new ObjectOpenHashSet<>(); + } + + @Override + public int size() { + return set.size(); + } + + @Override + public boolean isEmpty() { + return set.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return set.contains(o); + } + + @Override + public Iterator iterator() { + return set.iterator(); + } + + @Override + public Object[] toArray() { + return set.toArray(); + } + + @Override + public T[] toArray(T[] ts) { + return set.toArray(ts); + } + + @Override + public boolean add(T e) { + return set.add(e); + } + + @Override + public boolean remove(Object o) { + return set.remove(o); + } + + @Override + public boolean containsAll(Collection clctn) { + return set.containsAll(clctn); + } + + @Override + public boolean addAll(Collection clctn) { + throw new UnsupportedOperationException("Not supported operation."); + } + + @Override + public boolean retainAll(Collection clctn) { + throw new UnsupportedOperationException("Not supported operation."); + } + + @Override + public boolean removeAll(Collection clctn) { + throw new UnsupportedOperationException("Not supported operation."); + } + + @Override + public void clear() { + set.clear(); + } + + @Override + public boolean equals(Object o) { + return set.equals(o); + } + + @Override + public int hashCode() { + return set.hashCode(); + } + } + + protected final class WithNullDecorator implements Collection { + + private boolean hasNull() { + return !nullSet.isEmpty(); + } + + @Override + public int size() { + return (hasNull() ? 1 : 0) + map.size(); + } + + @Override + public boolean isEmpty() { + return !hasNull() && map.isEmpty(); + } + + @Override + public boolean contains(Object o) { + if (o == null && hasNull()) { + return true; + } else if (o != null) { + return map.containsKey((K) o); + } + return false; + } + + @Override + public Iterator iterator() { + return new WithNullDecorator.WithNullIterator(); + } + + @Override + public Object[] toArray() { + if (hasNull()) { + Object[] res = new Object[map.size() + 1]; + res[0] = null; + System.arraycopy(map.keySet().toArray(), 0, res, 1, map.size()); + return res; + } else { + return map.keySet().toArray(); + } + } + + @Override + public V[] toArray(V[] array) { + if (hasNull()) { + if (array.length < size()) { + array = (V[]) java.lang.reflect.Array + .newInstance(array.getClass().getComponentType(), map.size() + 1); + } + array[0] = null; + System.arraycopy(map.keySet().toArray(), 0, array, 1, map.size()); + return array; + } else { + return map.keySet().toArray(array); + } + } + + @Override + public boolean add(K e) { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public boolean containsAll(Collection clctn) { + for (Object o : clctn) { + if (o == null && nullSet.isEmpty()) { + return false; + } else if (o != null && !map.containsKey((K) o)) { + return false; + } + } + return true; + } + + @Override + public boolean addAll(Collection clctn) { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public boolean removeAll(Collection clctn) { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public boolean retainAll(Collection clctn) { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Not supported"); + } + + private final class WithNullIterator implements Iterator { + + private final Iterator mapIterator; + private boolean hasNull; + + public WithNullIterator() { + hasNull = hasNull(); + mapIterator = map.keySet().iterator(); + } + + @Override + public boolean hasNext() { + if (hasNull) { + return true; + } + return mapIterator.hasNext(); + } + + @Override + public K next() { + if (hasNull) { + hasNull = false; + return null; + } + return mapIterator.next(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Not supported operation."); + } + } + } + + private final class EntryIterator implements Iterator>> { + + private final Iterator>> mapIterator; + private NullEntry nullEntry; + + public EntryIterator() { + if (!nullSet.isEmpty()) { + nullEntry = new NullEntry(); + } + mapIterator = map.entrySet().iterator(); + } + + @Override + public boolean hasNext() { + if (nullEntry != null) { + return true; + } + return mapIterator.hasNext(); + } + + @Override + public Map.Entry> next() { + if (nullEntry != null) { + NullEntry ne = nullEntry; + nullEntry = null; + return ne; + } + return mapIterator.next(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Not supported operation."); + } + } + + private class NullEntry implements Map.Entry> { + + @Override + public K getKey() { + return null; + } + + @Override + public Set getValue() { + return nullSet; + } + + @Override + public Set setValue(Set v) { + throw new UnsupportedOperationException("Not supported operation."); + } + } + + private class LockableIterable implements Iterable { + + private final Iterable ite; + + public LockableIterable(Iterable ite) { + this.ite = ite; + } + + @Override + public Iterator iterator() { + return new LockableIterator<>(ite.iterator()); + } + } + + private class LockableIterator implements Iterator { + + private final Iterator itr; + + public LockableIterator(Iterator itr) { + this.itr = itr; + } + + @Override + public boolean hasNext() { + boolean n = itr.hasNext(); + if (!n && lock != null) { + lock.unlock(); + } + return n; + } + + @Override + public E next() { + return itr.next(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Not supported."); + } + } +} diff --git a/store/src/main/java/org/gephi/graph/impl/ColumnStore.java b/src/main/java/org/gephi/graph/impl/ColumnStore.java similarity index 63% rename from store/src/main/java/org/gephi/graph/impl/ColumnStore.java rename to src/main/java/org/gephi/graph/impl/ColumnStore.java index 79d0506b..c37dc96f 100644 --- a/store/src/main/java/org/gephi/graph/impl/ColumnStore.java +++ b/src/main/java/org/gephi/graph/impl/ColumnStore.java @@ -21,17 +21,13 @@ import it.unimi.dsi.fastutil.shorts.ShortRBTreeSet; import it.unimi.dsi.fastutil.shorts.ShortSortedSet; import java.util.ArrayList; -import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Set; -import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Column; import org.gephi.graph.api.ColumnIterable; -import org.gephi.graph.api.Configuration; -import org.gephi.graph.api.Edge; import org.gephi.graph.api.Element; -import org.gephi.graph.api.Node; +import org.gephi.graph.api.Origin; public class ColumnStore implements ColumnIterable { @@ -41,8 +37,9 @@ public class ColumnStore implements ColumnIterable { protected final static int NULL_ID = -1; protected final static short NULL_SHORT = Short.MIN_VALUE; // Configuration + protected final ConfigurationImpl configuration; + // GraphStore protected final GraphStore graphStore; - protected final Configuration configuration; // Element protected final Class elementType; // Columns @@ -54,49 +51,33 @@ public class ColumnStore implements ColumnIterable { // Version protected final List observers; // Locking (optional) - protected final TableLock lock; + protected final TableLockImpl lock; // Variables protected int length; public ColumnStore(Class elementType, boolean indexed) { - this(null, elementType, indexed); + this(null, elementType); } - public ColumnStore(GraphStore graphStore, Class elementType, boolean indexed) { + public ColumnStore(GraphStore graphStore, Class elementType) { if (MAX_SIZE >= Short.MAX_VALUE - Short.MIN_VALUE + 1) { throw new RuntimeException("Column Store size can't exceed 65534"); } this.graphStore = graphStore; - this.configuration = graphStore != null ? graphStore.configuration : new Configuration(); - this.lock = GraphStoreConfiguration.ENABLE_AUTO_LOCKING ? new TableLock() : null; + if (graphStore == null) { + // Used for testing only + configuration = new ConfigurationImpl(); + } else { + configuration = graphStore.configuration; + } + this.lock = configuration.isEnableAutoLocking() ? new TableLockImpl() : null; this.garbageQueue = new ShortRBTreeSet(); this.idMap = new Object2ShortOpenHashMap<>(MAX_SIZE); this.columns = new ColumnImpl[MAX_SIZE]; this.elementType = elementType; - this.indexStore = indexed ? new IndexStore<>(this) : null; + this.indexStore = new IndexStore<>(this); idMap.defaultReturnValue(NULL_SHORT); - this.observers = GraphStoreConfiguration.ENABLE_OBSERVERS ? new ArrayList<>() : null; - } - - private void updateConfiguration(Column changedColumn) { - String columnId = changedColumn.getId(); - if (Edge.class.equals(elementType)) { - if (columnId.equals(GraphStoreConfiguration.EDGE_WEIGHT_COLUMN_ID)) { - if (hasColumn(columnId)) { - Class edgeWeightColumnClass = getColumn(columnId).getTypeClass(); - configuration.setEdgeWeightType(edgeWeightColumnClass); - configuration.setEdgeWeightColumn(true); - } else { - configuration.setEdgeWeightColumn(false); - } - } else if (columnId.equals(GraphStoreConfiguration.ELEMENT_ID_COLUMN_ID)) { - configuration.setEdgeIdType(changedColumn.getTypeClass()); - } - } else if (Node.class.equals(elementType)) { - if (columnId.equals(GraphStoreConfiguration.ELEMENT_ID_COLUMN_ID)) { - configuration.setNodeIdType(changedColumn.getTypeClass()); - } - } + this.observers = new ArrayList<>(); } public void addColumn(final Column column) { @@ -125,9 +106,15 @@ public void addColumn(final Column column) { if (indexStore != null) { indexStore.addColumn(columnImpl); } - updateConfiguration(column); + + // Index attributes + if (graphStore != null && columnImpl.table != null) { + for (Element e : graphStore.getElements(columnImpl.table)) { + e.setAttribute(column, column.getDefaultValue()); + } + } } else { - throw new IllegalArgumentException("The column already exist"); + throw new IllegalArgumentException("The column " + column.getId() + " already exist"); } } finally { unlock(); @@ -138,26 +125,13 @@ public void removeColumn(final Column column) { checkNonNullColumnObject(column); lock(); - graphWriteLock(); try { final ColumnImpl columnImpl = (ColumnImpl) column; // Clean attributes if (graphStore != null && columnImpl.table != null) { - if (AttributeUtils.isNodeColumn(columnImpl)) { - for (Node n : graphStore.nodeStore) { - Object[] attributes = ((NodeImpl) n).attributes; - if (attributes.length > columnImpl.getIndex()) { - attributes[columnImpl.getIndex()] = null; - } - } - } else { - for (Edge e : graphStore.edgeStore) { - Object[] attributes = ((EdgeImpl) e).attributes; - if (attributes.length > columnImpl.getIndex()) { - attributes[columnImpl.getIndex()] = null; - } - } + for (Element e : graphStore.getElements(columnImpl.table)) { + ((ElementImpl) e).attributes.setAttribute(column, null); } } @@ -173,75 +147,52 @@ public void removeColumn(final Column column) { indexStore.removeColumn((ColumnImpl) column); } columnImpl.setStoreId(NULL_ID); - updateConfiguration(column); } finally { - graphWriteUnlock(); unlock(); } } public void removeColumn(final String key) { checkNonNullObject(key); - lock(); - try { - removeColumn(getColumn(key)); - } finally { - unlock(); + ColumnImpl col = getColumn(key); + if (col == null) { + throw new IllegalArgumentException("The column doesnt exist"); } + removeColumn(col); } public int getColumnIndex(final String key) { checkNonNullObject(key); - lock(); - try { - short id = idMap.getShort(key.toLowerCase()); - if (id == NULL_SHORT) { - throw new IllegalArgumentException("The column doesnt exist"); - } - return shortToInt(id); - } finally { - unlock(); + short id = idMap.getShort(key.toLowerCase()); + if (id == NULL_SHORT) { + throw new IllegalArgumentException("The column doesnt exist"); } + return shortToInt(id); } - public Column getColumnByIndex(final int index) { - lock(); - try { - if (index < 0 || index >= columns.length) { - throw new IllegalArgumentException("The column doesnt exist"); - } - ColumnImpl a = columns[index]; - if (a == null) { - throw new IllegalArgumentException("The column doesnt exist"); - } - return a; - } finally { - unlock(); + public ColumnImpl getColumnByIndex(final int index) { + if (index < 0 || index >= columns.length) { + throw new IllegalArgumentException("The column doesnt exist"); } + ColumnImpl a = columns[index]; + if (a == null) { + throw new IllegalArgumentException("The column doesnt exist"); + } + return a; } - public Column getColumn(final String key) { + public ColumnImpl getColumn(final String key) { checkNonNullObject(key); - lock(); - try { - short id = idMap.getShort(key.toLowerCase()); - if (id == NULL_SHORT) { - return null; - } - return columns[shortToInt(id)]; - } finally { - unlock(); + short id = idMap.getShort(key.toLowerCase()); + if (id == NULL_SHORT) { + return null; } + return columns[shortToInt(id)]; } public boolean hasColumn(String key) { checkNonNullObject(key); - lock(); - try { - return idMap.containsKey(key.toLowerCase()); - } finally { - unlock(); - } + return idMap.containsKey(key.toLowerCase()); } @Override @@ -298,49 +249,27 @@ public Set getColumnKeys() { } } - public void clear() { + public int size() { + return length - garbageQueue.size(); + } + + public int size(Origin origin) { + checkNonNullObject(origin); lock(); try { - // Clean attributes - if (graphStore != null) { - List cols = toList(); - int[] indices = new int[cols.size()]; - for (int i = 0; i < indices.length; i++) { - indices[i] = cols.get(i).getIndex(); - } - if (graphStore.nodeTable.store == this) { - for (Node n : graphStore.nodeStore) { - Object[] atts = ((NodeImpl) n).attributes; - for (int i = 0; i < indices.length; i++) { - atts[indices[i]] = null; - } - } - } else { - for (Edge e : graphStore.edgeStore) { - Object[] atts = ((EdgeImpl) e).attributes; - for (int i = 0; i < indices.length; i++) { - atts[indices[i]] = null; - } - } + int res = 0; + for (int i = 0; i < length; i++) { + ColumnImpl c = columns[i]; + if (c != null && c.origin.equals(origin)) { + res++; } } - - garbageQueue.clear(); - idMap.clear(); - length = 0; - Arrays.fill(columns, null); - if (indexStore != null) { - indexStore.clear(); - } + return res; } finally { unlock(); } } - public int size() { - return length - garbageQueue.size(); - } - protected TableObserverImpl createTableObserver(TableImpl table, boolean withDiff) { if (observers != null) { lock(); @@ -388,18 +317,6 @@ void unlock() { } } - void graphWriteLock() { - if (graphStore != null) { - graphStore.autoWriteLock(); - } - } - - void graphWriteUnlock() { - if (graphStore != null) { - graphStore.autoWriteUnlock(); - } - } - void checkNonNullObject(final Object o) { if (o == null) { throw new NullPointerException(); @@ -432,6 +349,9 @@ public ColumnStoreIterator() { @Override public boolean hasNext() { + if (pointer != null) { + return true; + } while (index < length && (pointer = columns[index++]) == null) { } if (pointer == null) { @@ -464,25 +384,49 @@ public boolean deepEquals(ColumnStore obj) { } Iterator itr1 = this.iterator(); Iterator itr2 = obj.iterator(); - while (itr1.hasNext()) { - if (!itr2.hasNext()) { - return false; + boolean itr1Closed = false; + boolean itr2Closed = false; + try { + while (itr1.hasNext()) { + if (!itr2.hasNext()) { + itr2Closed = true; + return false; + } + Column c1 = itr1.next(); + Column c2 = itr2.next(); + if (!c1.equals(c2)) { + return false; + } } - Column c1 = itr1.next(); - Column c2 = itr2.next(); - if (!c1.equals(c2)) { + itr1Closed = true; + if (itr2.hasNext()) { return false; } + itr2Closed = true; + return true; + } finally { + if (!itr1Closed) { + this.doBreak(); + } + if (!itr2Closed) { + obj.doBreak(); + } } - return true; } public int deepHashCode() { int hash = 3; hash = 11 * hash + (this.elementType != null ? this.elementType.hashCode() : 0); - ColumnStoreIterator itr = new ColumnStoreIterator(); - while (itr.hasNext()) { - hash = 11 * hash + itr.next().deepHashCode(); + lock(); + try { + for (int i = 0; i < length; i++) { + ColumnImpl c = columns[i]; + if (c != null) { + hash = 11 * hash + c.deepHashCode(); + } + } + } finally { + unlock(); } // TODO what about timestampmap return hash; diff --git a/store/src/main/java/org/gephi/graph/impl/ColumnVersion.java b/src/main/java/org/gephi/graph/impl/ColumnVersion.java similarity index 100% rename from store/src/main/java/org/gephi/graph/impl/ColumnVersion.java rename to src/main/java/org/gephi/graph/impl/ColumnVersion.java diff --git a/src/main/java/org/gephi/graph/impl/ConfigurationImpl.java b/src/main/java/org/gephi/graph/impl/ConfigurationImpl.java new file mode 100644 index 00000000..e89ae5ae --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/ConfigurationImpl.java @@ -0,0 +1,314 @@ +package org.gephi.graph.impl; + +import org.gephi.graph.api.Configuration; +import org.gephi.graph.api.TimeRepresentation; + +public class ConfigurationImpl { + + // Node Id Type (default String) + private final Class nodeIdType; + // Edge Id Type (default String) + private final Class edgeIdType; + // Edge Label Type (default String) + private final Class edgeLabelType; + // Edge Weight Type (default Double) + private final Class edgeWeightType; + // Time representation (default Timestamp) + private final TimeRepresentation timeRepresentation; + // Use edge weight column, or just double (default True) + private final boolean edgeWeightColumn; + // Automatically use read/write locks when iterating/writing graph elements + // (default True) + private final boolean enableAutoLocking; + // Automatically register edge types when adding elements (default True) + private final boolean enableAutoEdgeTypeRegistration; + // Enable reverse index for node attributes (default True) + private final boolean enableIndexNodes; + // Enable reverse index for edge attributes (default True) + private final boolean enableIndexEdges; + // Enable reverse index for timestamps (default True) + private final boolean enableIndexTime; + // Enable observers (default True) + private final boolean enableObservers; + // Node properties are X, Y, Color etc. (default True) + private final boolean enableNodeProperties; + // Edge properties are Color, etc. (default True) + private final boolean enableEdgeProperties; + // Enable spatial index (default False) + private final boolean enableSpatialIndex; + // Enable parallel edges of the same type (default True) + private final boolean enableParallelEdgesSameType; + + public ConfigurationImpl() { + nodeIdType = GraphStoreConfiguration.DEFAULT_NODE_ID_TYPE; + edgeIdType = GraphStoreConfiguration.DEFAULT_EDGE_ID_TYPE; + edgeLabelType = GraphStoreConfiguration.DEFAULT_EDGE_LABEL_TYPE; + edgeWeightType = GraphStoreConfiguration.DEFAULT_EDGE_WEIGHT_TYPE; + timeRepresentation = GraphStoreConfiguration.DEFAULT_TIME_REPRESENTATION; + edgeWeightColumn = GraphStoreConfiguration.DEFAULT_ENABLE_EDGE_WEIGHT_COLUMN; + enableAutoLocking = GraphStoreConfiguration.DEFAULT_ENABLE_AUTO_LOCKING; + enableAutoEdgeTypeRegistration = GraphStoreConfiguration.DEFAULT_ENABLE_AUTO_EDGE_TYPE_REGISTRATION; + enableIndexNodes = GraphStoreConfiguration.DEFAULT_ENABLE_INDEX_NODES; + enableIndexEdges = GraphStoreConfiguration.DEFAULT_ENABLE_INDEX_EDGES; + enableIndexTime = GraphStoreConfiguration.DEFAULT_ENABLE_INDEX_TIME; + enableObservers = GraphStoreConfiguration.DEFAULT_ENABLE_OBSERVERS; + enableNodeProperties = GraphStoreConfiguration.DEFAULT_ENABLE_NODE_PROPERTIES; + enableEdgeProperties = GraphStoreConfiguration.DEFAULT_ENABLE_EDGE_PROPERTIES; + enableSpatialIndex = GraphStoreConfiguration.DEFAULT_ENABLE_SPATIAL_INDEX; + enableParallelEdgesSameType = GraphStoreConfiguration.DEFAULT_ENABLE_PARALLEL_EDGES_SAME_TYPE; + } + + public ConfigurationImpl(Configuration configuration) { + nodeIdType = configuration.getNodeIdType(); + edgeIdType = configuration.getEdgeIdType(); + edgeLabelType = configuration.getEdgeLabelType(); + edgeWeightType = configuration.getEdgeWeightType(); + timeRepresentation = configuration.getTimeRepresentation(); + edgeWeightColumn = configuration.getEdgeWeightColumn(); + enableAutoLocking = configuration.isEnableAutoLocking(); + enableAutoEdgeTypeRegistration = configuration.isEnableAutoEdgeTypeRegistration(); + enableIndexNodes = configuration.isEnableIndexNodes(); + enableIndexEdges = configuration.isEnableIndexEdges(); + enableIndexTime = configuration.isEnableIndexTime(); + enableObservers = configuration.isEnableObservers(); + enableNodeProperties = configuration.isEnableNodeProperties(); + enableEdgeProperties = configuration.isEnableEdgeProperties(); + enableSpatialIndex = configuration.isEnableSpatialIndex(); + enableParallelEdgesSameType = configuration.isEnableParallelEdgesSameType(); + } + + public Configuration toConfiguration() { + return new ConfigurationProxy(this); + } + + public Class getNodeIdType() { + return nodeIdType; + } + + public Class getEdgeIdType() { + return edgeIdType; + } + + public Class getEdgeLabelType() { + return edgeLabelType; + } + + public Class getEdgeWeightType() { + return edgeWeightType; + } + + public TimeRepresentation getTimeRepresentation() { + return timeRepresentation; + } + + public boolean isEdgeWeightColumn() { + return edgeWeightColumn; + } + + public boolean isEnableAutoLocking() { + return enableAutoLocking; + } + + public boolean isEnableAutoEdgeTypeRegistration() { + return enableAutoEdgeTypeRegistration; + } + + public boolean isEnableIndexNodes() { + return enableIndexNodes; + } + + public boolean isEnableIndexEdges() { + return enableIndexEdges; + } + + public boolean isEnableIndexTime() { + return enableIndexTime; + } + + public boolean isEnableObservers() { + return enableObservers; + } + + public boolean isEnableNodeProperties() { + return enableNodeProperties; + } + + public boolean isEnableEdgeProperties() { + return enableEdgeProperties; + } + + public boolean isEnableSpatialIndex() { + return enableSpatialIndex; + } + + public boolean isEnableParallelEdgesSameType() { + return enableParallelEdgesSameType; + } + + // Used to return a Configuration instance + private static class ConfigurationProxy extends Configuration { + + private ConfigurationProxy(ConfigurationImpl impl) { + super(impl); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ConfigurationImpl)) { + return false; + } + + ConfigurationImpl that = (ConfigurationImpl) o; + + if (isEdgeWeightColumn() != that.isEdgeWeightColumn()) { + return false; + } + if (isEnableAutoLocking() != that.isEnableAutoLocking()) { + return false; + } + if (isEnableAutoEdgeTypeRegistration() != that.isEnableAutoEdgeTypeRegistration()) { + return false; + } + if (isEnableIndexNodes() != that.isEnableIndexNodes()) { + return false; + } + if (isEnableIndexEdges() != that.isEnableIndexEdges()) { + return false; + } + if (isEnableIndexTime() != that.isEnableIndexTime()) { + return false; + } + if (isEnableObservers() != that.isEnableObservers()) { + return false; + } + if (isEnableNodeProperties() != that.isEnableNodeProperties()) { + return false; + } + if (isEnableEdgeProperties() != that.isEnableEdgeProperties()) { + return false; + } + if (isEnableSpatialIndex() != that.isEnableSpatialIndex()) { + return false; + } + if (isEnableParallelEdgesSameType() != that.isEnableParallelEdgesSameType()) { + return false; + } + if (!getNodeIdType().equals(that.getNodeIdType())) { + return false; + } + if (!getEdgeIdType().equals(that.getEdgeIdType())) { + return false; + } + if (!getEdgeLabelType().equals(that.getEdgeLabelType())) { + return false; + } + if (!getEdgeWeightType().equals(that.getEdgeWeightType())) { + return false; + } + return getTimeRepresentation() == that.getTimeRepresentation(); + } + + @Override + public int hashCode() { + int result = getNodeIdType().hashCode(); + result = 31 * result + getEdgeIdType().hashCode(); + result = 31 * result + getEdgeLabelType().hashCode(); + result = 31 * result + getEdgeWeightType().hashCode(); + result = 31 * result + getTimeRepresentation().hashCode(); + result = 31 * result + (isEdgeWeightColumn() ? 1 : 0); + result = 31 * result + (isEnableAutoLocking() ? 1 : 0); + result = 31 * result + (isEnableAutoEdgeTypeRegistration() ? 1 : 0); + result = 31 * result + (isEnableIndexNodes() ? 1 : 0); + result = 31 * result + (isEnableIndexEdges() ? 1 : 0); + result = 31 * result + (isEnableIndexTime() ? 1 : 0); + result = 31 * result + (isEnableObservers() ? 1 : 0); + result = 31 * result + (isEnableNodeProperties() ? 1 : 0); + result = 31 * result + (isEnableEdgeProperties() ? 1 : 0); + result = 31 * result + (isEnableSpatialIndex() ? 1 : 0); + result = 31 * result + (isEnableParallelEdgesSameType() ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "ConfigurationImpl{" + "nodeIdType:" + nodeIdType + ", edgeIdType:" + edgeIdType + ", edgeLabelType:" + edgeLabelType + ", edgeWeightType:" + edgeWeightType + ", timeRepresentation:" + timeRepresentation + ", edgeWeightColumn:" + edgeWeightColumn + ", enableAutoLocking:" + enableAutoLocking + ", enableAutoEdgeTypeRegistration:" + enableAutoEdgeTypeRegistration + ", enableIndexNodes:" + enableIndexNodes + ", enableIndexEdges:" + enableIndexEdges + ", enableIndexTime:" + enableIndexTime + ", enableObservers:" + enableObservers + ", enableNodeProperties:" + enableNodeProperties + ", enableEdgeProperties:" + enableEdgeProperties + ", enableSpatialIndex:" + enableSpatialIndex + ", enableParallelEdgesSameType:" + enableParallelEdgesSameType + '}'; + } + + public String diffAsString(ConfigurationImpl other) { + ConfigurationImpl otherImpl = (ConfigurationImpl) other; + StringBuilder sb = new StringBuilder(); + if (!getNodeIdType().equals(otherImpl.getNodeIdType())) { + sb.append("nodeIdType: ").append(getNodeIdType()).append(" != ").append(otherImpl.getNodeIdType()) + .append("\n"); + } + if (!getEdgeIdType().equals(otherImpl.getEdgeIdType())) { + sb.append("edgeIdType: ").append(getEdgeIdType()).append(" != ").append(otherImpl.getEdgeIdType()) + .append("\n"); + } + if (!getEdgeLabelType().equals(otherImpl.getEdgeLabelType())) { + sb.append("edgeLabelType: ").append(getEdgeLabelType()).append(" != ").append(otherImpl.getEdgeLabelType()) + .append("\n"); + } + if (!getEdgeWeightType().equals(otherImpl.getEdgeWeightType())) { + sb.append("edgeWeightType: ").append(getEdgeWeightType()).append(" != ") + .append(otherImpl.getEdgeWeightType()).append("\n"); + } + if (getTimeRepresentation() != otherImpl.getTimeRepresentation()) { + sb.append("timeRepresentation: ").append(getTimeRepresentation()).append(" != ") + .append(otherImpl.getTimeRepresentation()).append("\n"); + } + if (isEdgeWeightColumn() != otherImpl.isEdgeWeightColumn()) { + sb.append("edgeWeightColumn: ").append(isEdgeWeightColumn()).append(" != ") + .append(otherImpl.isEdgeWeightColumn()).append("\n"); + } + if (isEnableAutoLocking() != otherImpl.isEnableAutoLocking()) { + sb.append("enableAutoLocking: ").append(isEnableAutoLocking()).append(" != ") + .append(otherImpl.isEnableAutoLocking()).append("\n"); + } + if (isEnableAutoEdgeTypeRegistration() != otherImpl.isEnableAutoEdgeTypeRegistration()) { + sb.append("enableAutoEdgeTypeRegistration: ").append(isEnableAutoEdgeTypeRegistration()).append(" != ") + .append(otherImpl.isEnableAutoEdgeTypeRegistration()).append("\n"); + } + if (isEnableIndexNodes() != otherImpl.isEnableIndexNodes()) { + sb.append("enableIndexNodes: ").append(isEnableIndexNodes()).append(" != ") + .append(otherImpl.isEnableIndexNodes()).append("\n"); + } + if (isEnableIndexEdges() != otherImpl.isEnableIndexEdges()) { + sb.append("enableIndexEdges: ").append(isEnableIndexEdges()).append(" != ") + .append(otherImpl.isEnableIndexEdges()).append("\n"); + } + if (isEnableIndexTime() != otherImpl.isEnableIndexTime()) { + sb.append("enableIndexTime: ").append(isEnableIndexTime()).append(" != ") + .append(otherImpl.isEnableIndexTime()).append("\n"); + } + if (isEnableObservers() != otherImpl.isEnableObservers()) { + sb.append("enableObservers: ").append(isEnableObservers()).append(" != ") + .append(otherImpl.isEnableObservers()).append("\n"); + } + if (isEnableNodeProperties() != otherImpl.isEnableNodeProperties()) { + sb.append("enableNodeProperties: ").append(isEnableNodeProperties()).append(" != ") + .append(otherImpl.isEnableNodeProperties()).append("\n"); + } + if (isEnableEdgeProperties() != otherImpl.isEnableEdgeProperties()) { + sb.append("enableEdgeProperties: ").append(isEnableEdgeProperties()).append(" != ") + .append(otherImpl.isEnableEdgeProperties()).append("\n"); + } + if (isEnableSpatialIndex() != otherImpl.isEnableSpatialIndex()) { + sb.append("enableSpatialIndex: ").append(isEnableSpatialIndex()).append(" != ") + .append(otherImpl.isEnableSpatialIndex()).append("\n"); + } + if (isEnableParallelEdgesSameType() != otherImpl.isEnableParallelEdgesSameType()) { + sb.append("enableParallelEdgesSameType: ").append(isEnableParallelEdgesSameType()).append(" != ") + .append(otherImpl.isEnableParallelEdgesSameType()).append("\n"); + } + // Remove last /n + if (sb.length() > 0) { + sb.setLength(sb.length() - 1); + } + return sb.toString(); + } +} diff --git a/src/main/java/org/gephi/graph/impl/DefaultColumnsImpl.java b/src/main/java/org/gephi/graph/impl/DefaultColumnsImpl.java new file mode 100644 index 00000000..a78eab91 --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/DefaultColumnsImpl.java @@ -0,0 +1,125 @@ +package org.gephi.graph.impl; + +import org.gephi.graph.api.Column; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Element; +import org.gephi.graph.api.GraphModel; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.Origin; + +public class DefaultColumnsImpl implements GraphModel.DefaultColumns { + + protected final GraphStore store; + + // Default columns (initialised at store creation) + protected final TableDefaultColumns nodeDefaultColumns; + protected final TableDefaultColumns edgeDefaultColumns; + + // Extra columns (temporary solution, until they are fully added as normal + // columns) + protected final ColumnImpl degreeColumn; + protected final ColumnImpl inDegreeColumn; + protected final ColumnImpl outDegreeColumn; + protected final ColumnImpl typeColumn; + + public DefaultColumnsImpl(GraphStore store) { + this.store = store; + this.nodeDefaultColumns = new TableDefaultColumns<>(store.nodeTable); + this.edgeDefaultColumns = new TableDefaultColumns<>(store.edgeTable); + + degreeColumn = new ColumnImpl(store.nodeTable, GraphStoreConfiguration.NODE_DEGREE_COLUMN_ID, Integer.class, + "Degree", null, Origin.PROPERTY, false, true); + inDegreeColumn = new ColumnImpl(store.nodeTable, GraphStoreConfiguration.NODE_IN_DEGREE_COLUMN_ID, + Integer.class, "In-Degree", null, Origin.PROPERTY, false, true); + outDegreeColumn = new ColumnImpl(store.nodeTable, GraphStoreConfiguration.NODE_OUT_DEGREE_COLUMN_ID, + Integer.class, "Out-Degree", null, Origin.PROPERTY, false, true); + typeColumn = new ColumnImpl(store.edgeTable, GraphStoreConfiguration.EDGE_TYPE_COLUMN_ID, Integer.class, "Type", + null, Origin.PROPERTY, false, true); + } + + // Used by serialization + protected ColumnImpl getColumn(TableImpl table, int storeId) { + TableDefaultColumns defaultColumns = table.isNodeTable() ? nodeDefaultColumns + : edgeDefaultColumns; + switch (storeId) { + case GraphStoreConfiguration.ELEMENT_ID_INDEX: + return defaultColumns.id; + case GraphStoreConfiguration.ELEMENT_LABEL_INDEX: + return defaultColumns.label; + case GraphStoreConfiguration.ELEMENT_TIMESET_INDEX: + return defaultColumns.timeset; + } + + if (table.isEdgeTable() && storeId == GraphStoreConfiguration.EDGE_WEIGHT_INDEX) { + return store.edgeTable.getColumn(storeId); + } + return null; + } + + @Override + public Column nodeId() { + return nodeDefaultColumns.id; + } + + @Override + public Column edgeId() { + return edgeDefaultColumns.id; + } + + public Column edgeWeight() { + return store.edgeTable.getColumn(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + } + + @Override + public Column nodeLabel() { + return nodeDefaultColumns.label; + } + + @Override + public Column edgeLabel() { + return edgeDefaultColumns.label; + } + + @Override + public Column nodeTimeSet() { + return nodeDefaultColumns.timeset; + } + + @Override + public Column edgeTimeSet() { + return edgeDefaultColumns.timeset; + } + + @Override + public Column degree() { + return degreeColumn; + } + + @Override + public Column inDegree() { + return inDegreeColumn; + } + + @Override + public Column outDegree() { + return outDegreeColumn; + } + + @Override + public Column edgeType() { + return typeColumn; + } + + protected static class TableDefaultColumns { + + protected final ColumnImpl id; + protected final ColumnImpl label; + protected final ColumnImpl timeset; + + public TableDefaultColumns(TableImpl table) { + this.id = table.getColumn(GraphStoreConfiguration.ELEMENT_ID_INDEX); + this.label = table.getColumn(GraphStoreConfiguration.ELEMENT_LABEL_INDEX); + this.timeset = table.getColumn(GraphStoreConfiguration.ELEMENT_TIMESET_INDEX); + } + } +} diff --git a/src/main/java/org/gephi/graph/impl/DegreeNoIndexImpl.java b/src/main/java/org/gephi/graph/impl/DegreeNoIndexImpl.java new file mode 100644 index 00000000..b1adc652 --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/DegreeNoIndexImpl.java @@ -0,0 +1,227 @@ +package org.gephi.graph.impl; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import org.gephi.graph.api.Column; +import org.gephi.graph.api.DirectedGraph; +import org.gephi.graph.api.Graph; +import org.gephi.graph.api.Node; + +public class DegreeNoIndexImpl implements ColumnIndexImpl { + + // Type + public enum DegreeType { + DEGREE, IN_DEGREE, OUT_DEGREE + } + + // Type + protected final DegreeType degreeType; + // Graph + protected final Graph graph; + + protected DegreeNoIndexImpl(Graph graph, DegreeType degreeType) { + this.graph = graph; + this.degreeType = degreeType; + } + + @Override + public int count(Integer value) { + checkNull(value); + + Iterator nodeIterator = graph.getNodes().iterator(); + int count = 0; + while (nodeIterator.hasNext()) { + Node node = nodeIterator.next(); + int degree = getDegree(node); + if (value == degree) { + count++; + } + } + return count; + } + + @Override + public Iterable get(Integer degree) { + checkNull(degree); + return new NodeWithDegreeIterable(degree); + } + + @Override + public Collection values() { + Iterator nodeIterator = graph.getNodes().iterator(); + Set set = new ObjectOpenHashSet<>(); + while (nodeIterator.hasNext()) { + Node node = nodeIterator.next(); + int degree = getDegree(node); + set.add(degree); + } + return set; + } + + @Override + public int countValues() { + return values().size(); + } + + @Override + public int countElements() { + return graph.getNodeCount(); + } + + @Override + public boolean isSortable() { + return true; + } + + @Override + public Integer getMinValue() { + Integer min = null; + Iterator nodeIterator = graph.getNodes().iterator(); + int minN = Integer.MAX_VALUE; + while (nodeIterator.hasNext()) { + Node node = nodeIterator.next(); + int degree = getDegree(node); + if (min == null || (degree < minN)) { + minN = degree; + min = degree; + } + } + return min; + } + + @Override + public Integer getMaxValue() { + Integer max = null; + Iterator nodeIterator = graph.getNodes().iterator(); + int maxN = Integer.MIN_VALUE; + while (nodeIterator.hasNext()) { + Node node = nodeIterator.next(); + int degree = getDegree(node); + if (max == null || (degree > maxN)) { + maxN = degree; + max = degree; + } + } + return max; + } + + @Override + public Column getColumn() { + switch (degreeType) { + case DEGREE: + return graph.getModel().defaultColumns().degree(); + case IN_DEGREE: + return graph.getModel().defaultColumns().inDegree(); + case OUT_DEGREE: + return graph.getModel().defaultColumns().outDegree(); + } + return null; + } + + @Override + public int getVersion() { + return ((GraphModelImpl) graph.getModel()).store.version.nodeVersion; + } + + @Override + public Iterator>> iterator() { + // TODO + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void clear() { + // Nothing to clear + } + + @Override + public void destroy() { + // Nothing to destroy + } + + @Override + public Integer putValue(Node element, Integer value) { + return value; + } + + @Override + public Integer replaceValue(Node element, Integer oldValue, Integer newValue) { + return newValue; + } + + @Override + public void removeValue(Node element, Integer value) { + // Nothing to remove + } + + private int getDegree(Node node) { + switch (degreeType) { + case DEGREE: + return graph.getDegree(node); + case IN_DEGREE: + return ((DirectedGraph) graph).getInDegree(node); + case OUT_DEGREE: + return ((DirectedGraph) graph).getOutDegree(node); + } + throw new RuntimeException(); + } + + private void checkNull(Integer value) { + if (value == null) { + throw new NullPointerException(); + } + } + + private class NodeWithDegreeIterable implements Iterable { + + private final Integer value; + + public NodeWithDegreeIterable(Integer degree) { + this.value = degree; + } + + @Override + public Iterator iterator() { + return new NodeWithDegreeIterator(value); + } + } + + private class NodeWithDegreeIterator implements Iterator { + + private final Iterator itr; + private final Integer value; + private Node pointer; + + public NodeWithDegreeIterator(Integer value) { + this.itr = graph.getNodes().iterator(); + this.value = value; + } + + @Override + public boolean hasNext() { + while (pointer == null && itr.hasNext()) { + Node node = itr.next(); + int degree = getDegree(node); + if (value == degree) { + pointer = node; + } + } + return pointer != null; + } + + @Override + public Node next() { + Node res = pointer; + pointer = null; + return res; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Not supported."); + } + } +} diff --git a/store/src/main/java/org/gephi/graph/impl/EdgeImpl.java b/src/main/java/org/gephi/graph/impl/EdgeImpl.java similarity index 61% rename from store/src/main/java/org/gephi/graph/impl/EdgeImpl.java rename to src/main/java/org/gephi/graph/impl/EdgeImpl.java index 3e0706af..c511f0be 100644 --- a/store/src/main/java/org/gephi/graph/impl/EdgeImpl.java +++ b/src/main/java/org/gephi/graph/impl/EdgeImpl.java @@ -1,536 +1,460 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.impl; - -import java.awt.Color; -import java.util.Map; -import org.gephi.graph.api.Column; -import org.gephi.graph.api.Estimator; -import org.gephi.graph.api.Interval; -import org.gephi.graph.api.Edge; -import org.gephi.graph.api.EdgeProperties; -import org.gephi.graph.api.GraphView; -import org.gephi.graph.api.Table; -import org.gephi.graph.api.types.IntervalMap; -import org.gephi.graph.api.types.TimeMap; -import org.gephi.graph.api.types.TimestampMap; -import static org.gephi.graph.impl.GraphStoreConfiguration.DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING; - -public class EdgeImpl extends ElementImpl implements Edge { - - // Const - protected static final byte DIRECTED_BYTE = 1; - protected static final byte MUTUAL_BYTE = 1 << 1; - // Final Data - protected final NodeImpl source; - protected final NodeImpl target; - protected final int type; - // Pointers - protected int storeId = EdgeStore.NULL_ID; - protected int nextOutEdge = EdgeStore.NULL_ID; - protected int nextInEdge = EdgeStore.NULL_ID; - protected int previousOutEdge = EdgeStore.NULL_ID; - protected int previousInEdge = EdgeStore.NULL_ID; - // Flags - protected byte flags; - // Props - protected final EdgePropertiesImpl properties; - - public EdgeImpl(Object id, GraphStore graphStore, NodeImpl source, NodeImpl target, int type, double weight, boolean directed) { - super(id, graphStore); - checkIdType(id); - this.source = source; - this.target = target; - this.flags = (byte) (directed ? 1 : 0); - this.type = type; - this.properties = GraphStoreConfiguration.ENABLE_EDGE_PROPERTIES ? new EdgePropertiesImpl() : null; - this.attributes = new Object[GraphStoreConfiguration.EDGE_WEIGHT_INDEX + 1]; - this.attributes[GraphStoreConfiguration.ELEMENT_ID_INDEX] = id; - if (graphStore == null || graphStore.configuration.getEdgeWeightType().equals(Double.class)) { - this.attributes[GraphStoreConfiguration.EDGE_WEIGHT_INDEX] = weight; - } - } - - public EdgeImpl(Object id, NodeImpl source, NodeImpl target, int type, double weight, boolean directed) { - this(id, null, source, target, type, weight, directed); - } - - @Override - public NodeImpl getSource() { - return source; - } - - @Override - public NodeImpl getTarget() { - return target; - } - - @Override - public double getWeight() { - synchronized (this) { - Object weightObject = attributes[GraphStoreConfiguration.EDGE_WEIGHT_INDEX]; - if (weightObject instanceof Double) { - return (Double) weightObject; - } else { - return getWeight(graphStore.getView()); - } - } - } - - @Override - public boolean hasDynamicWeight() { - return !Double.class.equals(graphStore.configuration.getEdgeWeightType()); - } - - @Override - public void setWeight(double weight, double timestamp) { - checkTimeRepresentationTimestamp(); - setTimeWeight(weight, timestamp); - } - - @Override - public void setWeight(double weight, Interval interval) { - checkTimeRepresentationInterval(); - setTimeWeight(weight, interval); - } - - private void setTimeWeight(double weight, Object timeObject) { - checkWeightDynamicType(); - - boolean res; - synchronized (this) { - Object oldValue = attributes[GraphStoreConfiguration.EDGE_WEIGHT_INDEX]; - TimeMap dynamicValue = null; - if (oldValue == null) { - try { - attributes[GraphStoreConfiguration.EDGE_WEIGHT_INDEX] = dynamicValue = (TimeMap) graphStore.configuration - .getEdgeWeightType().newInstance(); - } catch (InstantiationException | IllegalAccessException ex) { - throw new RuntimeException(ex); - } - } else { - dynamicValue = (TimeMap) oldValue; - } - res = dynamicValue.put(timeObject, weight); - } - TimeIndexStore timeIndexStore = getTimeIndexStore(); - if (res && timeIndexStore != null && isValid()) { - timeIndexStore.add(timeObject); - } - ColumnStore columnStore = getColumnStore(); - if (res && columnStore != null && isValid()) { - Column column = columnStore.getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); - ((ColumnImpl) column).incrementVersion(this); - } - } - - @Override - public double getWeight(double timestamp) { - synchronized (this) { - Object weightValue = attributes[GraphStoreConfiguration.EDGE_WEIGHT_INDEX]; - if (weightValue instanceof Double) { - throw new IllegalStateException("The weight is static, call getWeight() instead"); - } - - TimeMap dynamicValue = (TimeMap) weightValue; - if (dynamicValue == null) { - return DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING; - } - - if (dynamicValue instanceof IntervalMap) { - return (Double) ((IntervalMap) dynamicValue) - .get(new Interval(timestamp, timestamp), DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING); - } else { - return (Double) dynamicValue.get(timestamp, DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING); - } - } - } - - @Override - public double getWeight(Interval interval) { - synchronized (this) { - Object weightValue = attributes[GraphStoreConfiguration.EDGE_WEIGHT_INDEX]; - if (weightValue instanceof Double) { - throw new IllegalStateException("The weight is static, call getWeight() instead"); - } - - TimeMap dynamicValue = (TimeMap) weightValue; - if (dynamicValue == null) { - return DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING; - } - - Estimator estimator = getColumnStore().getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX) - .getEstimator(); - if (estimator == null) { - estimator = GraphStoreConfiguration.DEFAULT_ESTIMATOR; - } - - Double doubleVal = (Double) dynamicValue.get(interval, estimator); - return doubleVal != null ? doubleVal : DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING; - } - } - - @Override - public double getWeight(GraphView view) { - synchronized (this) { - Object value = attributes[GraphStoreConfiguration.EDGE_WEIGHT_INDEX]; - if (value instanceof TimeMap) { - Interval interval = view.getTimeInterval(); - checkViewExist((GraphView) view); - - TimeMap dynamicValue = (TimeMap) value; - Estimator estimator = getColumnStore().getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX) - .getEstimator(); - if (estimator == null) { - estimator = GraphStoreConfiguration.DEFAULT_ESTIMATOR; - } - - Double doubleVal = (Double) dynamicValue.get(interval, estimator); - return doubleVal != null ? doubleVal : DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING; - } else if (value == null) { - return DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING; - } else { - // Must be double - return (Double) value; - } - } - } - - @Override - public Iterable getWeights() { - synchronized (this) { - Object weightValue = attributes[GraphStoreConfiguration.EDGE_WEIGHT_INDEX]; - if (weightValue instanceof Double) { - throw new IllegalStateException("The weight is static, call getWeight() instead"); - } - TimeMap dynamicValue = (TimeMap) weightValue; - Object[] values = dynamicValue.toValuesArray(); - if (dynamicValue instanceof TimestampMap) { - return new TimeAttributeIterable(((TimestampMap) dynamicValue).getTimestamps(), values); - } else if (dynamicValue instanceof IntervalMap) { - return new TimeAttributeIterable(((IntervalMap) dynamicValue).toKeysArray(), values); - } - } - return TimeAttributeIterable.EMPTY_ITERABLE; - } - - @Override - public int getType() { - return type; - } - - @Override - public Object getTypeLabel() { - graphStore.autoReadLock(); - try { - return graphStore.edgeTypeStore.getLabel(type); - } finally { - graphStore.autoReadUnlock(); - } - } - - @Override - public void setWeight(double weight) { - checkWeightStaticType(); - - final Object oldValue; - synchronized (this) { - oldValue = attributes[GraphStoreConfiguration.EDGE_WEIGHT_INDEX]; - attributes[GraphStoreConfiguration.EDGE_WEIGHT_INDEX] = weight; - } - - ColumnStore columnStore = getColumnStore(); - if (columnStore != null && isValid()) { - Column column = columnStore.getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); - ((ColumnImpl) column).incrementVersion(this); - if (column.isIndexed()) { - columnStore.indexStore.set(column, oldValue, weight, this); - } - } - } - - public int getNextOutEdge() { - return nextOutEdge; - } - - public int getNextInEdge() { - return nextInEdge; - } - - public int getPreviousOutEdge() { - return previousOutEdge; - } - - public int getPreviousInEdge() { - return previousInEdge; - } - - @Override - public int getStoreId() { - return storeId; - } - - public void setStoreId(int id) { - this.storeId = id; - } - - public long getLongId() { - return EdgeStore.getLongId(source, target, isDirected()); - } - - @Override - public boolean isDirected() { - return (flags & DIRECTED_BYTE) == 1; - } - - protected void setMutual(boolean mutual) { - if (isDirected()) { - if (mutual) { - flags |= MUTUAL_BYTE; - } else { - flags &= ~MUTUAL_BYTE; - } - } - } - - protected boolean isMutual() { - return (flags & MUTUAL_BYTE) == MUTUAL_BYTE; - } - - @Override - public boolean isSelfLoop() { - return source == target; - } - - @Override - public Table getTable() { - if (graphStore != null) { - return graphStore.edgeTable; - } - return null; - } - - @Override - ColumnStore getColumnStore() { - if (graphStore != null) { - return graphStore.edgeTable.store; - } - return null; - } - - @Override - TimeIndexStore getTimeIndexStore() { - if (graphStore != null) { - return graphStore.timeStore.edgeIndexStore; - } - return null; - } - - @Override - boolean isValid() { - return storeId != EdgeStore.NULL_ID; - } - - @Override - public float r() { - return properties.r(); - } - - @Override - public float g() { - return properties.g(); - } - - @Override - public float b() { - return properties.b(); - } - - @Override - public float alpha() { - return properties.alpha(); - } - - @Override - public TextPropertiesImpl getTextProperties() { - return properties.getTextProperties(); - } - - protected void setEdgeProperties(EdgePropertiesImpl edgeProperties) { - properties.rgba = edgeProperties.rgba; - if (properties.textProperties != null) { - properties.setTextProperties(edgeProperties.textProperties); - } - } - - @Override - public int getRGBA() { - return properties.rgba; - } - - @Override - public Color getColor() { - return properties.getColor(); - } - - @Override - public void setR(float r) { - properties.setR(r); - } - - @Override - public void setG(float g) { - properties.setG(g); - } - - @Override - public void setB(float b) { - properties.setB(b); - } - - @Override - public void setAlpha(float a) { - properties.setAlpha(a); - } - - @Override - public void setColor(Color color) { - properties.setColor(color); - } - - final void checkIdType(Object id) { - if (graphStore != null && !id.getClass().equals(graphStore.configuration.getEdgeIdType())) { - throw new IllegalArgumentException( - "The id class does not match with the expected type (" + graphStore.configuration.getEdgeIdType() - .getName() + ")"); - } - } - - final void checkWeightStaticType() { - if (graphStore != null && !Double.class.equals(graphStore.configuration.getEdgeWeightType())) { - throw new IllegalArgumentException( - "The weight class does not match with the expected type (" + graphStore.configuration - .getEdgeWeightType().getName() + ")"); - } - } - - final void checkWeightDynamicType() { - if (graphStore != null && Double.class.equals(graphStore.configuration.getEdgeWeightType())) { - throw new IllegalArgumentException( - "The weight class does not match with the expected type (" + graphStore.configuration - .getEdgeWeightType().getName() + ")"); - } - } - - protected static class EdgePropertiesImpl implements EdgeProperties { - - protected final TextPropertiesImpl textProperties; - protected int rgba; - - public EdgePropertiesImpl() { - textProperties = new TextPropertiesImpl(); - this.rgba = 255 << 24; // Alpha set to 1 - } - - @Override - public float r() { - return ((rgba >> 16) & 0xFF) / 255f; - } - - @Override - public float g() { - return ((rgba >> 8) & 0xFF) / 255f; - } - - @Override - public float b() { - return (rgba & 0xFF) / 255f; - } - - @Override - public float alpha() { - return ((rgba >> 24) & 0xFF) / 255f; - } - - @Override - public int getRGBA() { - return rgba; - } - - @Override - public TextPropertiesImpl getTextProperties() { - return textProperties; - } - - protected void setTextProperties(TextPropertiesImpl textProperties) { - this.textProperties.rgba = textProperties.rgba; - this.textProperties.size = textProperties.size; - this.textProperties.text = textProperties.text; - this.textProperties.visible = textProperties.visible; - } - - @Override - public Color getColor() { - return new Color(rgba, true); - } - - @Override - public void setR(float r) { - rgba = (rgba & 0xFF00FFFF) | (((int) (r * 255f)) << 16); - } - - @Override - public void setG(float g) { - rgba = (rgba & 0xFFFF00FF) | ((int) (g * 255f)) << 8; - } - - @Override - public void setB(float b) { - rgba = (rgba & 0xFFFFFF00) | ((int) (b * 255f)); - } - - @Override - public void setAlpha(float a) { - rgba = (rgba & 0xFFFFFF) | ((int) (a * 255f)) << 24; - } - - @Override - public void setColor(Color color) { - rgba = (color.getAlpha() << 24) | color.getRGB(); - } - - public int deepHashCode() { - int hash = 3; - hash = 29 * hash + this.rgba; - hash = 29 * hash + (this.textProperties != null ? this.textProperties.deepHashCode() : 0); - return hash; - } - - public boolean deepEquals(EdgePropertiesImpl obj) { - if (obj == null) { - return false; - } - if (this.rgba != obj.rgba) { - return false; - } - if (this.textProperties != obj.textProperties && (this.textProperties == null || !this.textProperties - .deepEquals(obj.textProperties))) { - return false; - } - return true; - } - } -} +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import static org.gephi.graph.impl.GraphStoreConfiguration.DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING; + +import java.awt.Color; +import java.util.Map; +import org.gephi.graph.api.Column; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.EdgeProperties; +import org.gephi.graph.api.GraphView; +import org.gephi.graph.api.Interval; +import org.gephi.graph.api.Table; + +public class EdgeImpl extends ElementImpl implements Edge { + + // Const + protected static final byte DIRECTED_BYTE = 1; + protected static final byte MUTUAL_BYTE = 1 << 1; + // Final Data + protected final NodeImpl source; + protected final NodeImpl target; + // Edge type + protected int type; + // Pointers + protected int storeId = EdgeStore.NULL_ID; + protected int nextOutEdge = EdgeStore.NULL_ID; + protected int nextInEdge = EdgeStore.NULL_ID; + protected int previousOutEdge = EdgeStore.NULL_ID; + protected int previousInEdge = EdgeStore.NULL_ID; + // Flags + protected byte flags; + // Props + protected final EdgePropertiesImpl properties; + + public EdgeImpl(Object id, GraphStore graphStore, NodeImpl source, NodeImpl target, int type, double weight, boolean directed) { + super(id, graphStore); + checkIdType(id); + this.source = source; + this.target = target; + this.flags = (byte) (directed ? 1 : 0); + this.type = type; + this.properties = graphStore == null || graphStore.configuration.isEnableEdgeProperties() + ? new EdgePropertiesImpl() : null; + if (graphStore == null || graphStore.configuration.getEdgeWeightType().equals(Double.class)) { + this.attributes.setAttribute(GraphStoreConfiguration.EDGE_WEIGHT_INDEX, weight); + } + } + + public EdgeImpl(Object id, NodeImpl source, NodeImpl target, int type, double weight, boolean directed) { + this(id, null, source, target, type, weight, directed); + } + + @Override + public NodeImpl getSource() { + return source; + } + + @Override + public NodeImpl getTarget() { + return target; + } + + @Override + public double getWeight() { + synchronized (this) { + Object weightObject = attributes.getAttribute(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + if (weightObject instanceof Double) { + return (Double) weightObject; + } else { + return getWeight(graphStore.getView()); + } + } + } + + @Override + public boolean hasDynamicWeight() { + return !Double.class.equals(graphStore.configuration.getEdgeWeightType()); + } + + @Override + public void setWeight(double weight, double timestamp) { + checkWeightDynamicType(); + setAttribute(graphStore.defaultColumns.edgeWeight(), weight, timestamp); + } + + @Override + public void setWeight(double weight, Interval interval) { + checkWeightDynamicType(); + setAttribute(graphStore.defaultColumns.edgeWeight(), weight, interval); + } + + @Override + public double getWeight(double timestamp) { + Column column = getColumnStore().getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + checkStaticWeight(column); + Double doubleVal = (Double) attributes.getAttribute(column, timestamp, null); + return doubleVal != null ? doubleVal : DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING; + } + + @Override + public double getWeight(Interval interval) { + Column column = getColumnStore().getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + checkStaticWeight(column); + Double doubleVal = (Double) attributes.getAttribute(column, interval, getEstimator(column)); + return doubleVal != null ? doubleVal : DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING; + } + + @Override + public double getWeight(GraphView view) { + checkViewExist(view); + Column column = getColumnStore().getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + if (column.isDynamicAttribute()) { + return getWeight(view.getTimeInterval()); + } else { + return (Double) attributes.getAttribute(column); + } + } + + @Override + public Iterable getWeights() { + Column column = getColumnStore().getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + checkStaticWeight(column); + return attributes.getAttributes(column); + } + + @Override + public int getType() { + return type; + } + + @Override + public void setType(int type) { + graphStore.autoWriteLock(); + try { + graphStore.edgeStore.setEdgeType(this, type); + } finally { + graphStore.autoWriteUnlock(); + } + } + + @Override + public Object getTypeLabel() { + graphStore.autoReadLock(); + try { + return graphStore.edgeTypeStore.getLabel(type); + } finally { + graphStore.autoReadUnlock(); + } + } + + @Override + public void setWeight(double weight) { + checkWeightStaticType(); + + Column column = getColumnStore().getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + setAttribute(column, weight); + } + + public int getNextOutEdge() { + return nextOutEdge; + } + + public int getNextInEdge() { + return nextInEdge; + } + + public int getPreviousOutEdge() { + return previousOutEdge; + } + + public int getPreviousInEdge() { + return previousInEdge; + } + + @Override + public int getStoreId() { + return storeId; + } + + public void setStoreId(int id) { + this.storeId = id; + } + + public long getLongId() { + return EdgeStore.getLongId(source, target, isDirected()); + } + + @Override + public boolean isDirected() { + return (flags & DIRECTED_BYTE) == 1; + } + + protected void setMutual(boolean mutual) { + if (isDirected()) { + if (mutual) { + flags |= MUTUAL_BYTE; + } else { + flags &= ~MUTUAL_BYTE; + } + } + } + + @Override + public boolean isMutual() { + return (flags & MUTUAL_BYTE) == MUTUAL_BYTE; + } + + @Override + public boolean isSelfLoop() { + return source == target; + } + + @Override + public Table getTable() { + if (graphStore != null) { + return graphStore.edgeTable; + } + return null; + } + + @Override + ColumnStore getColumnStore() { + if (graphStore != null) { + return graphStore.edgeTable.store; + } + return null; + } + + @Override + DefaultColumnsImpl.TableDefaultColumns getDefaultColumns() { + if (graphStore != null) { + return graphStore.defaultColumns.edgeDefaultColumns; + } + return null; + } + + @Override + TimeIndexStore getTimeIndexStore() { + if (graphStore != null) { + return graphStore.timeStore.edgeIndexStore; + } + return null; + } + + @Override + boolean isValid() { + return storeId != EdgeStore.NULL_ID; + } + + @Override + public float r() { + return properties.r(); + } + + @Override + public float g() { + return properties.g(); + } + + @Override + public float b() { + return properties.b(); + } + + @Override + public float alpha() { + return properties.alpha(); + } + + @Override + public TextPropertiesImpl getTextProperties() { + return properties.getTextProperties(); + } + + protected void setEdgeProperties(EdgePropertiesImpl edgeProperties) { + properties.rgba = edgeProperties.rgba; + if (properties.textProperties != null) { + properties.setTextProperties(edgeProperties.textProperties); + } + } + + @Override + public int getRGBA() { + return properties.rgba; + } + + @Override + public Color getColor() { + return properties.getColor(); + } + + @Override + public void setR(float r) { + properties.setR(r); + } + + @Override + public void setG(float g) { + properties.setG(g); + } + + @Override + public void setB(float b) { + properties.setB(b); + } + + @Override + public void setAlpha(float a) { + properties.setAlpha(a); + } + + @Override + public void setColor(Color color) { + properties.setColor(color); + } + + final void checkIdType(Object id) { + if (graphStore != null && !id.getClass().equals(graphStore.configuration.getEdgeIdType())) { + throw new IllegalArgumentException( + "The id class does not match with the expected type (" + graphStore.configuration.getEdgeIdType() + .getName() + ")"); + } + } + + final void checkWeightStaticType() { + if (graphStore != null && !Double.class.equals(graphStore.configuration.getEdgeWeightType())) { + throw new IllegalArgumentException( + "The weight class does not match with the expected type (" + graphStore.configuration + .getEdgeWeightType().getName() + ")"); + } + } + + final void checkWeightDynamicType() { + if (graphStore != null && Double.class.equals(graphStore.configuration.getEdgeWeightType())) { + throw new IllegalArgumentException( + "The weight class does not match with the expected type (" + graphStore.configuration + .getEdgeWeightType().getName() + ")"); + } + } + + final void checkStaticWeight(Column column) { + if (!column.isDynamicAttribute()) { + throw new IllegalStateException("The weight is static, call getWeight() instead"); + } + } + + protected static class EdgePropertiesImpl implements EdgeProperties { + + protected final TextPropertiesImpl textProperties; + protected int rgba; + + public EdgePropertiesImpl() { + textProperties = new TextPropertiesImpl(); + this.rgba = 255 << 24; // Alpha set to 1 + } + + @Override + public float r() { + return ((rgba >> 16) & 0xFF) / 255f; + } + + @Override + public float g() { + return ((rgba >> 8) & 0xFF) / 255f; + } + + @Override + public float b() { + return (rgba & 0xFF) / 255f; + } + + @Override + public float alpha() { + return ((rgba >> 24) & 0xFF) / 255f; + } + + @Override + public int getRGBA() { + return rgba; + } + + @Override + public TextPropertiesImpl getTextProperties() { + return textProperties; + } + + protected void setTextProperties(TextPropertiesImpl textProperties) { + this.textProperties.rgba = textProperties.rgba; + this.textProperties.size = textProperties.size; + this.textProperties.text = textProperties.text; + this.textProperties.visible = textProperties.visible; + } + + @Override + public Color getColor() { + return new Color(rgba, true); + } + + @Override + public void setR(float r) { + rgba = (rgba & 0xFF00FFFF) | (((int) (r * 255f)) << 16); + } + + @Override + public void setG(float g) { + rgba = (rgba & 0xFFFF00FF) | ((int) (g * 255f)) << 8; + } + + @Override + public void setB(float b) { + rgba = (rgba & 0xFFFFFF00) | ((int) (b * 255f)); + } + + @Override + public void setAlpha(float a) { + rgba = (rgba & 0xFFFFFF) | ((int) (a * 255f)) << 24; + } + + @Override + public void setColor(Color color) { + rgba = (color.getAlpha() << 24) | color.getRGB(); + } + + public int deepHashCode() { + int hash = 3; + hash = 29 * hash + this.rgba; + hash = 29 * hash + (this.textProperties != null ? this.textProperties.deepHashCode() : 0); + return hash; + } + + public boolean deepEquals(EdgePropertiesImpl obj) { + if (obj == null) { + return false; + } + if (this.rgba != obj.rgba) { + return false; + } + if (this.textProperties != obj.textProperties && (this.textProperties == null || !this.textProperties + .deepEquals(obj.textProperties))) { + return false; + } + return true; + } + } +} diff --git a/src/main/java/org/gephi/graph/impl/EdgeIterableWrapper.java b/src/main/java/org/gephi/graph/impl/EdgeIterableWrapper.java new file mode 100644 index 00000000..d51e7b38 --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/EdgeIterableWrapper.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import java.util.Iterator; +import java.util.Spliterator; +import java.util.function.Supplier; +import java.util.stream.StreamSupport; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.EdgeIterable; + +public class EdgeIterableWrapper extends ElementIterableWrapper implements EdgeIterable { + + public EdgeIterableWrapper(Supplier> iteratorSupplier, GraphLockImpl lock) { + super(iteratorSupplier, lock); + } + + public EdgeIterableWrapper(Supplier> iteratorSupplier, Supplier> spliteratorSupplier, GraphLockImpl lock) { + super(iteratorSupplier, spliteratorSupplier, lock); + } + + @Override + public Edge[] toArray() { + if (parallelPossible && lock != null) { + lock.readLock(); + try { + return StreamSupport.stream(spliterator(), true).toArray(Edge[]::new); + } finally { + lock.readUnlock(); + } + } + return StreamSupport.stream(spliterator(), parallelPossible).toArray(Edge[]::new); + } +} diff --git a/store/src/main/java/org/gephi/graph/impl/EdgeStore.java b/src/main/java/org/gephi/graph/impl/EdgeStore.java similarity index 73% rename from store/src/main/java/org/gephi/graph/impl/EdgeStore.java rename to src/main/java/org/gephi/graph/impl/EdgeStore.java index 36df694c..730003ca 100644 --- a/store/src/main/java/org/gephi/graph/impl/EdgeStore.java +++ b/src/main/java/org/gephi/graph/impl/EdgeStore.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.gephi.graph.impl; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenCustomHashMap; @@ -24,8 +25,16 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.gephi.graph.api.Edge; import org.gephi.graph.api.EdgeIterable; import org.gephi.graph.api.Node; @@ -35,8 +44,19 @@ public class EdgeStore implements Collection, EdgeIterable { // Const protected final static int NULL_ID = -1; protected final static int NODE_BITS = 31; - protected static final Iterator EMPTY_EDGE_ITERATOR = Collections. emptyList().iterator(); - + protected static final Iterator EMPTY_EDGE_ITERATOR = Collections.emptyIterator(); + // Locking (optional) + protected final GraphLockImpl lock; + // Version + protected final GraphVersion version; + // Types counting (optional) + protected final EdgeTypeStore edgeTypeStore; + // View store + protected final GraphViewStore viewStore; + // Spatial index + protected final SpatialIndexImpl spatialIndex; + // Configuration + protected final ConfigurationImpl configuration; // Data protected int size; protected int garbageSize; @@ -46,22 +66,11 @@ public class EdgeStore implements Collection, EdgeIterable { protected EdgeBlock currentBlock; protected Object2IntOpenHashMap dictionary; protected Long2ObjectOpenCustomHashMap[] longDictionary; - // Stats + protected int typeSize[]; protected int undirectedSize; protected int mutualEdgesSize; protected int[] mutualEdgesTypeSize; - // Locking (optional) - protected final GraphLock lock; - // Version - protected final GraphVersion version; - // Types counting (optional) - protected final EdgeTypeStore edgeTypeStore; - // View store - protected final GraphViewStore viewStore; - - // Spatial index - protected final GraphStoreSpatialContextImpl spatialIndex; public EdgeStore() { initStore(); @@ -70,15 +79,29 @@ public EdgeStore() { this.viewStore = null; this.version = null; this.spatialIndex = null; + this.configuration = new ConfigurationImpl(); } - public EdgeStore(final EdgeTypeStore edgeTypeStore, final GraphStoreSpatialContextImpl spatialIndex, final GraphLock lock, final GraphViewStore viewStore, final GraphVersion graphVersion) { + public EdgeStore(final EdgeTypeStore edgeTypeStore, final SpatialIndexImpl spatialIndex, final ConfigurationImpl configuration, final GraphLockImpl lock, final GraphViewStore viewStore, final GraphVersion graphVersion) { initStore(); this.lock = lock; this.edgeTypeStore = edgeTypeStore; this.viewStore = viewStore; this.version = graphVersion; this.spatialIndex = spatialIndex; + this.configuration = configuration == null ? new ConfigurationImpl() : configuration; + } + + protected static long getLongId(NodeImpl source, NodeImpl target, boolean directed) { + if (directed) { + long edgeId = ((long) source.storeId) << NODE_BITS; + edgeId = edgeId | (long) (target.storeId); + return edgeId; + } else { + long edgeId = ((long) (source.storeId > target.storeId ? source.storeId : target.storeId)) << NODE_BITS; + edgeId = edgeId | (long) (source.storeId > target.storeId ? target.storeId : source.storeId); + return edgeId; + } } private void initStore() { @@ -96,6 +119,7 @@ private void initStore() { GraphStoreConfiguration.EDGESTORE_DEFAULT_DICTIONARY_SIZE, GraphStoreConfiguration.EDGESTORE_DICTIONARY_LOAD_FACTOR, new DictionaryHashStrategy()); this.mutualEdgesTypeSize = new int[GraphStoreConfiguration.EDGESTORE_DEFAULT_TYPE_COUNT]; + this.typeSize = new int[GraphStoreConfiguration.EDGESTORE_DEFAULT_TYPE_COUNT]; } private void ensureCapacity(final int capacity) { @@ -191,6 +215,9 @@ private void ensureLongDictionaryCapacity(int type) { int[] newSizeArray = new int[type + 1]; System.arraycopy(mutualEdgesTypeSize, 0, newSizeArray, 0, length); mutualEdgesTypeSize = newSizeArray; + newSizeArray = new int[type + 1]; + System.arraycopy(typeSize, 0, newSizeArray, 0, length); + typeSize = newSizeArray; } } @@ -242,7 +269,7 @@ private void removeOutEdge(EdgeImpl edge) { EdgeImpl[] headOutArray = source.headOut; headOutArray[type] = nextOutEdge; if (nextOutEdge == null && type > GraphStoreConfiguration.EDGESTORE_DEFAULT_TYPE_COUNT - 1 && type == headOutArray.length - 1) { - trimHeadOutCapacity(source, type - 1); + trimHeadOutCapacity(source, headOutArray.length - 1); } } else { EdgeImpl previousOutEdge = get(previousOutEdgeId); @@ -269,7 +296,7 @@ private void removeInEdge(EdgeImpl edge) { EdgeImpl[] headInArray = target.headIn; headInArray[type] = nextInEdge; if (nextInEdge == null && type > GraphStoreConfiguration.EDGESTORE_DEFAULT_TYPE_COUNT - 1 && type == headInArray.length - 1) { - trimHeadInCapacity(target, type - 1); + trimHeadInCapacity(target, headInArray.length - 1); } } else { EdgeImpl previousInEdge = get(previousInEdgeId); @@ -291,10 +318,6 @@ public void clear() { edge.setStoreId(EdgeStore.NULL_ID); } - if (this.spatialIndex != null) { - this.spatialIndex.clearEdges(); - } - initStore(); } @@ -309,14 +332,14 @@ public int undirectedSize() { public int size(int type) { if (type < longDictionary.length) { - return longDictionary[type].size(); + return typeSize[type]; } return 0; } public int undirectedSize(int type) { if (type < longDictionary.length) { - return longDictionary[type].size() - mutualEdgesTypeSize[type]; + return typeSize[type] - mutualEdgesTypeSize[type]; } return 0; } @@ -331,6 +354,49 @@ public EdgeStoreIterator iterator() { return new EdgeStoreIterator(); } + @Override + public Spliterator spliterator() { + int end = blocksCount; + return new EdgeSpliterator(0, end); + } + + @Override + public Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + @Override + public Stream parallelStream() { + return StreamSupport.stream(spliterator(), true); + } + + public Spliterator spliteratorUndirected() { + int end = blocksCount; + return new FilteredSizedEdgeSpliterator(0, end, e -> !isUndirectedToIgnore(e), undirectedSize()); + } + + public Spliterator spliteratorType(int type, boolean undirected) { + int end = blocksCount; + return new FilteredSizedEdgeSpliterator(0, end, + e -> e.getType() == type && (!undirected || !isUndirectedToIgnore(e)), + undirected ? undirectedSize(type) : size(type)); + } + + public Spliterator spliteratorSelfLoop() { + int end = blocksCount; + return new FilteredEdgeSpliterator(0, end, EdgeImpl::isSelfLoop); + } + + protected Spliterator newFilteredSpliterator(java.util.function.Predicate filter) { + int end = blocksCount; + return new FilteredEdgeSpliterator(0, end, filter); + } + + protected Spliterator newFilteredSizedSpliterator(java.util.function.Predicate filter, int size) { + int end = blocksCount; + return new FilteredSizedEdgeSpliterator(0, end, filter, size); + } + public EdgeStoreIterator iteratorUndirected() { return new UndirectedEdgeStoreIterator(); } @@ -339,6 +405,10 @@ public SelfLoopIterator iteratorSelfLoop() { return new SelfLoopIterator(); } + public EdgeTypeIterator iteratorType(final int type, final boolean undirected) { + return new EdgeTypeIterator(type, undirected); + } + public EdgeOutIterator edgeOutIterator(final Node node) { checkValidNodeObject(node); return new EdgeOutIterator((NodeImpl) node); @@ -349,14 +419,18 @@ public EdgeInIterator edgeInIterator(final Node node) { return new EdgeInIterator((NodeImpl) node); } - public EdgeInOutIterator edgeIterator(final Node node) { + public EdgeInOutIterator edgeIterator(final Node node, boolean locking) { checkValidNodeObject(node); - return new EdgeInOutIterator((NodeImpl) node); + return new EdgeInOutIterator((NodeImpl) node, locking); + } + + public EdgeInOutMultiIterator edgeIterator(final Iterator nodeIterator, boolean locking) { + return new EdgeInOutMultiIterator(nodeIterator, locking); } - public Iterator edgeUndirectedIterator(final Node node) { + public Iterator edgeUndirectedIterator(final Node node, boolean locking) { checkValidNodeObject(node); - return undirectedIterator(new EdgeInOutIterator((NodeImpl) node)); + return undirectedIterator(new EdgeInOutIterator((NodeImpl) node, locking)); } public EdgeTypeOutIterator edgeOutIterator(final Node node, int type) { @@ -401,7 +475,7 @@ public NeighborsIterator neighborInIterator(final Node node, int type) { public NeighborsIterator neighborIterator(Node node) { checkValidNodeObject(node); - return new NeighborsUndirectedIterator((NodeImpl) node, new EdgeInOutIterator((NodeImpl) node)); + return new NeighborsUndirectedIterator((NodeImpl) node, new EdgeInOutIterator((NodeImpl) node, true)); } public NeighborsIterator neighborIterator(final Node node, int type) { @@ -427,6 +501,14 @@ public EdgeImpl get(int id) { return blocks[id / GraphStoreConfiguration.EDGESTORE_BLOCK_SIZE].get(id); } + // Only used for Graph.getEdgeByStoreId + public EdgeImpl getForGetByStoreId(int id) { + if (id < 0 || !isValidIndex(id)) { + return null; + } + return blocks[id / GraphStoreConfiguration.EDGESTORE_BLOCK_SIZE].get(id); + } + public EdgeImpl get(final Object id) { checkNonNullObject(id); @@ -547,6 +629,123 @@ private EdgeImpl getMutual(final EdgeImpl edge) { return get(edge.target, edge.source, edge.type, false); } + public boolean setEdgeType(final Edge e, int type) { + checkNonNullEdgeObject(e); + + if (e.getType() == type) { + return false; + } + + int oldType = e.getType(); + EdgeImpl edge = (EdgeImpl) e; + if (edge.storeId != EdgeStore.NULL_ID) { + ensureLongDictionaryCapacity(type); + Long2ObjectOpenCustomHashMap newDico = longDictionary[type]; + + long longId = getLongId(edge.source, edge.target, edge.isDirected()); + int[] newDicoValue = newDico.get(longId); + if (newDicoValue != null && !configuration.isEnableParallelEdgesSameType()) { + return false; + } + + edgeTypeStore.registerEdgeType(type); + boolean wasMutual = edge.isMutual(); + removeFromDico(edge, edge.storeId); + typeSize[oldType]--; + + removeOutEdge(edge); + removeInEdge(edge); + edge.type = type; + insertOutEdge(edge); + insertInEdge(edge); + + addToDico(newDico, newDicoValue, edge, longId); + typeSize[type]++; + + if (viewStore != null) { + viewStore.setEdgeType(edge, oldType, wasMutual); + } + + incrementVersion(); + } else { + edge.type = type; + } + + return true; + } + + private void removeFromDico(EdgeImpl edge, int id) { + int type = edge.type; + NodeImpl source = edge.source; + NodeImpl target = edge.target; + boolean directed = edge.isDirected(); + + Long2ObjectOpenCustomHashMap dico = longDictionary[type]; + long longId = getLongId(source, target, directed); + int[] dicoValue = dico.get(longId); + if (dicoValue.length == 1) { + dico.remove(longId); + } else { + int[] newDicoValue = new int[dicoValue.length - 1]; + int j = 0; + for (int i = 0; i < dicoValue.length; i++) { + int v = dicoValue[i]; + if (v != id) { + newDicoValue[j++] = v; + } + } + dico.put(longId, newDicoValue); + } + + if (directed && !edge.isSelfLoop()) { + int[] index = longDictionary[type].get(getLongId(edge.target, edge.source, true)); + if (index != null) { + for (int i = 0; i < index.length; i++) { + EdgeImpl mutual = get(index[i]); + if (mutual.isMutual()) { + edge.setMutual(false); + + mutual.setMutual(false); + source.mutualDegree--; + target.mutualDegree--; + mutualEdgesSize--; + mutualEdgesTypeSize[type]--; + break; + } + } + } + } + } + + private void addToDico(Long2ObjectOpenCustomHashMap dico, int[] dicoValue, EdgeImpl edge, long longId) { + if (dicoValue == null) { + dicoValue = new int[] { edge.storeId }; + } else { + dicoValue = Arrays.copyOf(dicoValue, dicoValue.length + 1); + dicoValue[dicoValue.length - 1] = edge.storeId; + } + dico.put(longId, dicoValue); + + if (edge.isDirected() && !edge.isSelfLoop()) { + int type = edge.type; + int[] index = longDictionary[type].get(getLongId(edge.target, edge.source, true)); + if (index != null) { + for (int i = 0; i < index.length; i++) { + EdgeImpl mutual = get(index[i]); + if (!mutual.isMutual()) { + mutual.setMutual(true); + edge.setMutual(true); + edge.source.mutualDegree++; + edge.target.mutualDegree++; + mutualEdgesSize++; + mutualEdgesTypeSize[type]++; + break; + } + } + } + } + } + @Override public boolean add(final Edge e) { checkNonNullEdgeObject(e); @@ -566,10 +765,13 @@ public boolean add(final Edge e) { Long2ObjectOpenCustomHashMap dico = longDictionary[type]; long longId = getLongId(source, target, directed); int[] dicoValue = dico.get(longId); - if (dicoValue != null && !GraphStoreConfiguration.ENABLE_PARALLEL_EDGES) { + if (dicoValue != null && !configuration.isEnableParallelEdgesSameType()) { return false; } + if (edgeTypeStore != null) { + edgeTypeStore.registerEdgeType(type); + } incrementVersion(); if (garbageSize > 0) { @@ -594,46 +796,19 @@ public boolean add(final Edge e) { source.outDegree++; target.inDegree++; - if (dicoValue == null) { - dicoValue = new int[] { edge.storeId }; - } else { - dicoValue = Arrays.copyOf(dicoValue, dicoValue.length + 1); - dicoValue[dicoValue.length - 1] = edge.storeId; - } - dico.put(longId, dicoValue); + addToDico(dico, dicoValue, edge, longId); if (viewStore != null) { viewStore.addEdge(edge); } edge.indexAttributes(); - if (directed && !edge.isSelfLoop()) { - int[] index = longDictionary[type].get(getLongId(edge.target, edge.source, true)); - if (index != null) { - for (int i = 0; i < index.length; i++) { - EdgeImpl mutual = get(index[i]); - if (!mutual.isMutual()) { - mutual.setMutual(true); - edge.setMutual(true); - source.mutualDegree++; - target.mutualDegree++; - mutualEdgesSize++; - mutualEdgesTypeSize[type]++; - break; - } - } - } - } - if (!directed) { undirectedSize++; } - if (this.spatialIndex != null) { - this.spatialIndex.addEdge(e); - } - size++; + typeSize[type]++; return true; } else if (isValidIndex(edge.storeId) && get(edge.storeId) == edge) { return false; @@ -657,11 +832,7 @@ public boolean remove(final Object o) { viewStore.removeEdge(edge); } - if (this.spatialIndex != null) { - this.spatialIndex.removeEdge(edge); - } - - edge.clearAttributes(); + edge.destroyAttributes(); int storeIndex = id / GraphStoreConfiguration.EDGESTORE_BLOCK_SIZE; EdgeBlock block = blocks[storeIndex]; @@ -678,6 +849,7 @@ public boolean remove(final Object o) { target.inDegree--; size--; + typeSize[edge.type]--; garbageSize++; dictionary.remove(edge.getId()); trimDictionary(); @@ -697,43 +869,7 @@ public boolean remove(final Object o) { } } - int type = edge.type; - - Long2ObjectOpenCustomHashMap dico = longDictionary[type]; - long longId = getLongId(source, target, directed); - int[] dicoValue = dico.get(longId); - if (dicoValue.length == 1) { - dico.remove(longId); - } else { - int[] newDicoValue = new int[dicoValue.length - 1]; - int j = 0; - for (int i = 0; i < dicoValue.length; i++) { - int v = dicoValue[i]; - if (v != id) { - newDicoValue[j++] = v; - } - } - dico.put(longId, newDicoValue); - } - - if (directed && !edge.isSelfLoop()) { - int[] index = longDictionary[type].get(getLongId(edge.target, edge.source, true)); - if (index != null) { - for (int i = 0; i < index.length; i++) { - EdgeImpl mutual = get(index[i]); - if (mutual.isMutual()) { - edge.setMutual(true); - - mutual.setMutual(false); - source.mutualDegree--; - target.mutualDegree--; - mutualEdgesSize--; - mutualEdgesTypeSize[type]--; - break; - } - } - } - } + removeFromDico(edge, id); if (!directed) { undirectedSize--; @@ -766,6 +902,16 @@ public boolean containsId(final Object id) { return dictionary.containsKey(id); } + public boolean containsAnyType(NodeImpl source, NodeImpl target) { + int typeLength = longDictionary.length; + for (int i = 0; i < typeLength; i++) { + if (contains(source, target, i)) { + return true; + } + } + return false; + } + public boolean contains(NodeImpl source, NodeImpl target, int type) { checkNonNullObject(source); checkNonNullObject(target); @@ -859,6 +1005,21 @@ public Collection toCollection() { return list; } + @Override + public Set toSet() { + readLock(); + + Set set = new HashSet<>(size); + EdgeStoreIterator itr = iterator(); + while (itr.hasNext()) { + EdgeImpl n = itr.next(); + set.add(n); + } + + readUnlock(); + return set; + } + @Override public boolean containsAll(Collection c) { checkCollection(c); @@ -872,7 +1033,7 @@ public boolean containsAll(Collection c) { } return found == c.size(); } - return false; + return true; } @Override @@ -937,8 +1098,9 @@ public boolean retainAll(Collection c) { } } return changed; - } else { + } else if (size > 0) { clear(); + return true; } return false; } @@ -1127,6 +1289,9 @@ private void incrementVersion() { if (version != null) { version.incrementAndGetEdgeVersion(); } + if (spatialIndex != null) { + spatialIndex.incrementVersion(); + } } boolean isUndirectedToIgnore(EdgeImpl edge) { @@ -1137,18 +1302,6 @@ int maxStoreId() { return currentBlock.offset + currentBlock.nodeLength; } - protected static long getLongId(NodeImpl source, NodeImpl target, boolean directed) { - if (directed) { - long edgeId = ((long) source.storeId) << NODE_BITS; - edgeId = edgeId | (long) (target.storeId); - return edgeId; - } else { - long edgeId = ((long) (source.storeId > target.storeId ? source.storeId : target.storeId)) << NODE_BITS; - edgeId = edgeId | (long) (source.storeId > target.storeId ? target.storeId : source.storeId); - return edgeId; - } - } - protected static class EdgeBlock { protected final int offset; @@ -1203,6 +1356,19 @@ public void clear() { } } + private static class DictionaryHashStrategy implements LongHash.Strategy { + + @Override + public int hashCode(long l) { + return (int) (l ^ (l >>> 32)); + } + + @Override + public boolean equals(long l1, long l2) { + return l1 == l2; + } + } + protected class EdgeStoreIterator implements Iterator { protected int blockIndex; @@ -1280,6 +1446,37 @@ public void remove() { } } + protected final class EdgeTypeIterator extends EdgeStoreIterator { + + private final int type; + private final boolean undirected; + + public EdgeTypeIterator(int type, boolean undirected) { + super(); + this.type = type; + this.undirected = undirected; + } + + @Override + public boolean hasNext() { + pointer = null; + while (pointer == null) { + if (!super.hasNext()) { + return false; + } + if (pointer.getType() != type || (undirected && isUndirectedToIgnore(pointer))) { + pointer = null; + } + } + return true; + } + + @Override + public void remove() { + EdgeStore.this.remove(pointer); + } + } + protected final class SelfLoopIterator extends EdgeStoreIterator { public SelfLoopIterator() { @@ -1301,10 +1498,15 @@ public boolean hasNext() { } } - protected final class EdgeInOutIterator implements Iterator { + /** + * Abstract base class for iterating over edges connected to nodes. Provides common logic for handling both incoming + * and outgoing edges. + */ + protected abstract class AbstractEdgeInOutIterator implements Iterator { - protected final int outTypeLength; - protected final int inTypeLength; + protected final boolean locking; + protected int outTypeLength; + protected int inTypeLength; protected EdgeImpl[] outArray; protected EdgeImpl[] inArray; protected int typeIndex = 0; @@ -1312,17 +1514,35 @@ protected final class EdgeInOutIterator implements Iterator { protected EdgeImpl lastEdge; protected boolean out = true; - public EdgeInOutIterator(NodeImpl node) { - readLock(); + protected AbstractEdgeInOutIterator(boolean locking) { + this.locking = locking; + if (locking) { + readLock(); + } + } + + /** + * Initialize arrays for the current node. Called when starting iteration for a new node. + */ + protected void initializeForNode(NodeImpl node) { outArray = node.headOut; outTypeLength = outArray.length; inArray = node.headIn; inTypeLength = inArray.length; + typeIndex = 0; + pointer = null; + out = true; } + /** + * Called when the current node has no more edges. Should return true if there are more nodes to process, false + * otherwise. + */ + protected abstract boolean moveToNextNode(); + @Override public boolean hasNext() { - if (pointer == null) { + while (pointer == null) { if (out) { while (pointer == null && typeIndex < outTypeLength) { pointer = outArray[typeIndex++]; @@ -1347,8 +1567,13 @@ public boolean hasNext() { } if (pointer == null) { - readUnlock(); - return false; + // No more edges for current node, try next node + if (!moveToNextNode()) { + if (locking) { + readUnlock(); + } + return false; + } } } return true; @@ -1389,6 +1614,54 @@ public void remove() { } } + /** + * Iterator for edges connected to a single node (both incoming and outgoing). + */ + protected final class EdgeInOutIterator extends AbstractEdgeInOutIterator { + + public EdgeInOutIterator(NodeImpl node, boolean locking) { + super(locking); + initializeForNode(node); + } + + @Override + protected boolean moveToNextNode() { + // Single node iterator - no more nodes to process + return false; + } + } + + /** + * Iterator for edges connected to multiple nodes (both incoming and outgoing). Iterates through all edges of all + * provided nodes without creating separate iterators. + */ + protected final class EdgeInOutMultiIterator extends AbstractEdgeInOutIterator { + + private final Iterator nodeIterator; + + public EdgeInOutMultiIterator(Iterator nodeIterator, boolean locking) { + super(locking); + this.nodeIterator = nodeIterator; + // Initialize with first node if available + if (nodeIterator.hasNext()) { + NodeImpl node = nodeIterator.next(); + checkValidNodeObject(node); + initializeForNode(node); + } + } + + @Override + protected boolean moveToNextNode() { + if (nodeIterator.hasNext()) { + NodeImpl node = nodeIterator.next(); + checkValidNodeObject(node); + initializeForNode(node); + return true; + } + return false; + } + } + protected final class EdgeOutIterator implements Iterator { protected final int typeLength; @@ -1814,16 +2087,218 @@ public void remove() { } } - private static class DictionaryHashStrategy implements LongHash.Strategy { + private class EdgeSpliterator implements Spliterator { + + protected final int endBlockExclusive; + protected int blockIndex; + protected int indexInBlock; + protected EdgeImpl[] currentArray; + protected int currentLength; + protected final int expectedVersion; + protected int totalSize; + protected int consumed; + + EdgeSpliterator(int startBlock, int endBlockExclusive) { + this(startBlock, endBlockExclusive, EdgeStore.this.size()); + } + + EdgeSpliterator(int startBlock, int endBlockExclusive, int totalSize) { + this.blockIndex = startBlock; + this.endBlockExclusive = endBlockExclusive; + this.expectedVersion = version != null ? version.getEdgeVersion() : 0; + this.consumed = 0; + + // Use the total store size for the root spliterator (covering all blocks) + if (startBlock == 0 && endBlockExclusive == blocksCount) { + this.totalSize = totalSize; + } else { + // For split spliterators, compute proportionally + this.totalSize = computeExactSize(startBlock, endBlockExclusive); + } + + if (startBlock < endBlockExclusive) { + EdgeStore.EdgeBlock b = blocks[startBlock]; + currentArray = b.backingArray; + currentLength = b.nodeLength; + indexInBlock = 0; + } else { + currentArray = null; + currentLength = 0; + indexInBlock = 0; + } + } + + private int computeExactSize(int start, int end) { + int sum = 0; + for (int i = start; i < end; i++) { + EdgeStore.EdgeBlock b = blocks[i]; + if (b != null) { + // Exact count: nodeLength minus garbageLength + sum += (b.nodeLength - b.garbageLength); + } + } + return sum; + } + + protected void advanceBlock() { + blockIndex++; + if (blockIndex < endBlockExclusive) { + EdgeStore.EdgeBlock b = blocks[blockIndex]; + currentArray = b.backingArray; + currentLength = b.nodeLength; + indexInBlock = 0; + } else { + currentArray = null; + currentLength = 0; + indexInBlock = 0; + } + } + + protected void checkForComodification() { + if (version != null && expectedVersion != version.getEdgeVersion()) { + throw new ConcurrentModificationException(); + } + } @Override - public int hashCode(long l) { - return (int) (l ^ (l >>> 32)); + public boolean tryAdvance(Consumer action) { + checkForComodification(); + while (currentArray != null) { + while (indexInBlock < currentLength) { + EdgeImpl n = currentArray[indexInBlock++]; + if (n != null) { + consumed++; + action.accept(n); + return true; + } + } + advanceBlock(); + } + return false; + } + + protected EdgeSpliterator createSplit(int startBlock, int endBlockExclusive) { + return new EdgeSpliterator(startBlock, endBlockExclusive); } @Override - public boolean equals(long l1, long l2) { - return l1 == l2; + public Spliterator trySplit() { + // Only split at block boundaries to preserve encounter order + if (indexInBlock != 0) { + return null; + } + + int currentPos = blockIndex; + int remainingBlocks = endBlockExclusive - currentPos; + + if (remainingBlocks <= 1) { + return null; + } + + int mid = currentPos + remainingBlocks / 2; + + // Create left half + EdgeSpliterator left = createSplit(currentPos, mid); + + // Update this spliterator to become the right half + blockIndex = mid; + if (mid < endBlockExclusive) { + EdgeStore.EdgeBlock b = blocks[mid]; + currentArray = b.backingArray; + currentLength = b.nodeLength; + indexInBlock = 0; + } else { + currentArray = null; + currentLength = 0; + indexInBlock = 0; + } + + // Update this spliterator size + this.totalSize = totalSize - left.totalSize; + + return left; + } + + @Override + public long estimateSize() { + // Use the exact totalSize minus what we've consumed + long remaining = totalSize - consumed; + return remaining < 0 ? 0 : remaining; + } + + @Override + public int characteristics() { + return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SIZED | Spliterator.SUBSIZED; } } + + private class FilteredSizedEdgeSpliterator extends EdgeSpliterator { + + protected final Predicate filter; + // True only for the root spliterator, where totalSize is the caller-supplied, + // filter-aware count. Sub-ranges created by trySplit fall back to the inherited, + // unfiltered block count and must drop SIZED. + protected boolean exactSize; + + FilteredSizedEdgeSpliterator(int startBlock, int endBlockExclusive, Predicate filter, int totalSize) { + super(startBlock, endBlockExclusive, totalSize); + this.filter = filter; + this.exactSize = (startBlock == 0 && endBlockExclusive == blocksCount); + } + + @Override + public boolean tryAdvance(Consumer action) { + checkForComodification(); + while (currentArray != null) { + while (indexInBlock < currentLength) { + EdgeImpl n = currentArray[indexInBlock++]; + if (n != null && filter.test(n)) { + consumed++; + action.accept(n); + return true; + } + } + advanceBlock(); + } + return false; + } + + @Override + public Spliterator trySplit() { + Spliterator left = super.trySplit(); + if (left != null) { + // Once split, neither half can guarantee an exact filtered size, so drop SIZED. + this.exactSize = false; + ((FilteredSizedEdgeSpliterator) left).exactSize = false; + } + return left; + } + + protected EdgeSpliterator createSplit(int startBlock, int endBlockExclusive) { + return new FilteredSizedEdgeSpliterator(startBlock, endBlockExclusive, filter, totalSize); + } + + @Override + public int characteristics() { + int base = Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL; + return exactSize ? base | Spliterator.SIZED : base; + } + } + + private final class FilteredEdgeSpliterator extends FilteredSizedEdgeSpliterator { + + FilteredEdgeSpliterator(int startBlock, int endBlockExclusive, Predicate filter) { + super(startBlock, endBlockExclusive, filter, EdgeStore.this.size()); + } + + protected EdgeSpliterator createSplit(int startBlock, int endBlockExclusive) { + return new FilteredEdgeSpliterator(startBlock, endBlockExclusive, filter); + } + + @Override + public int characteristics() { + return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL; + } + } + } diff --git a/src/main/java/org/gephi/graph/impl/EdgeTypeNoIndexImpl.java b/src/main/java/org/gephi/graph/impl/EdgeTypeNoIndexImpl.java new file mode 100644 index 00000000..d47f2205 --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/EdgeTypeNoIndexImpl.java @@ -0,0 +1,109 @@ +package org.gephi.graph.impl; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import org.gephi.graph.api.Column; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Graph; + +public class EdgeTypeNoIndexImpl implements ColumnIndexImpl { + + // Graph + protected final Graph graph; + + protected EdgeTypeNoIndexImpl(Graph graph) { + this.graph = graph; + } + + @Override + public int count(Object label) { + return graph.getEdgeCount(labelToType(label)); + } + + @Override + public Iterable get(Object label) { + return graph.getEdges(labelToType(label)); + } + + @Override + public Collection values() { + return Arrays.asList(graph.getModel().getEdgeTypeLabels(false)); + } + + @Override + public int countValues() { + return values().size(); + } + + @Override + public int countElements() { + return graph.getEdgeCount(); + } + + @Override + public boolean isSortable() { + return false; + } + + @Override + public Number getMinValue() { + throw new UnsupportedOperationException("Edge type index is not sortable"); + } + + @Override + public Number getMaxValue() { + throw new UnsupportedOperationException("Edge type index is not sortable"); + } + + @Override + public Column getColumn() { + return graph.getModel().defaultColumns().edgeType(); + } + + @Override + public int getVersion() { + return ((GraphModelImpl) graph.getModel()).store.version.edgeVersion; + } + + @Override + public Iterator>> iterator() { + // TODO + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void clear() { + // Nothing to clear + } + + @Override + public void destroy() { + // Nothing to destroy + } + + @Override + public Object putValue(Edge element, Object value) { + return value; + } + + @Override + public Object replaceValue(Edge element, Object oldValue, Object newValue) { + return newValue; + } + + @Override + public void removeValue(Edge element, Object value) { + // Nothing to remove + } + + private int labelToType(Object label) { + int type = graph.getModel().getEdgeType(label); + if (type == -1) { + throw new IllegalArgumentException("Edge label " + label + " doesn't exist"); + } + return type; + } +} diff --git a/store/src/main/java/org/gephi/graph/impl/EdgeTypeStore.java b/src/main/java/org/gephi/graph/impl/EdgeTypeStore.java similarity index 89% rename from store/src/main/java/org/gephi/graph/impl/EdgeTypeStore.java rename to src/main/java/org/gephi/graph/impl/EdgeTypeStore.java index fe63df6d..55e5f393 100644 --- a/store/src/main/java/org/gephi/graph/impl/EdgeTypeStore.java +++ b/src/main/java/org/gephi/graph/impl/EdgeTypeStore.java @@ -22,7 +22,6 @@ import it.unimi.dsi.fastutil.shorts.ShortRBTreeSet; import it.unimi.dsi.fastutil.shorts.ShortSortedSet; import java.util.Arrays; -import org.gephi.graph.api.Configuration; import org.gephi.graph.impl.utils.MapDeepEquals; public class EdgeTypeStore { @@ -34,17 +33,17 @@ public class EdgeTypeStore { // Config public final static int MAX_SIZE = 65534; // Data - protected final Configuration configuration; + protected final ConfigurationImpl configuration; protected final Object2ShortMap labelMap; protected final Short2ObjectMap idMap; protected final ShortSortedSet garbageQueue; protected int length; public EdgeTypeStore() { - this(new Configuration()); + this(new ConfigurationImpl()); } - public EdgeTypeStore(Configuration config) { + public EdgeTypeStore(ConfigurationImpl config) { if (MAX_SIZE >= Short.MAX_VALUE - Short.MIN_VALUE + 1) { throw new RuntimeException("Edge Type Store size can't exceed 65534"); } @@ -76,6 +75,17 @@ public Object getLabel(final int id) { return idMap.get(intToShort(id)); } + public void registerEdgeType(int type) { + if (!contains(type)) { + if (configuration.isEnableAutoEdgeTypeRegistration()) { + addType(String.valueOf(type), type); + } else { + throw new UnsupportedOperationException( + "The type " + type + " doesn't exist, and edge type auto registration is disabled (from Configuration)"); + } + } + } + public int addType(final Object label) { checkType(label); @@ -103,7 +113,8 @@ public boolean addType(final Object label, final int id) { if (foundId != NULL_SHORT && foundId != givenId) { throw new RuntimeException("This label '" + label + "' is already assigned to a different id"); } else if (idMap.containsKey(givenId)) { - if ((label == null && idMap.get(givenId) == null) || idMap.get(givenId).equals(label)) { + if ((label == null && idMap.get(givenId) == null) || (idMap.get(givenId) != null && idMap.get(givenId) + .equals(label))) { return false; } else { throw new RuntimeException("This id '" + id + "' is already assigned to a different label"); @@ -219,10 +230,10 @@ private void checkType(final Object o) { if (o != null) { Class cl = o.getClass(); if (!(cl.equals(Integer.class) || cl.equals(String.class) || cl.equals(Float.class) || cl - .equals(Double.class) || cl.equals(Short.class) || cl.equals(Byte.class) || cl.equals(Long.class) || cl - .equals(Character.class) || cl.equals(Boolean.class))) { - throw new IllegalArgumentException( - "The type id is " + cl.getCanonicalName() + " but must be a primitive type (int, string, long...)"); + .equals(Double.class) || cl.equals(Short.class) || cl.equals(Byte.class) || cl + .equals(Long.class) || cl.equals(Character.class) || cl.equals(Boolean.class))) { + throw new IllegalArgumentException("The type id is " + cl + .getCanonicalName() + " but must be a primitive type (int, string, long...)"); } if (!configuration.getEdgeLabelType().equals(o.getClass())) { throw new IllegalArgumentException("The expected type was " + configuration.getEdgeLabelType() diff --git a/store/src/main/java/org/gephi/graph/impl/ElementImpl.java b/src/main/java/org/gephi/graph/impl/ElementImpl.java similarity index 51% rename from store/src/main/java/org/gephi/graph/impl/ElementImpl.java rename to src/main/java/org/gephi/graph/impl/ElementImpl.java index 187dc910..f69c4612 100644 --- a/store/src/main/java/org/gephi/graph/impl/ElementImpl.java +++ b/src/main/java/org/gephi/graph/impl/ElementImpl.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.gephi.graph.impl; import java.util.List; @@ -21,21 +22,10 @@ import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Column; import org.gephi.graph.api.ColumnIterable; -import org.gephi.graph.api.Estimator; -import org.gephi.graph.api.Interval; -import org.gephi.graph.api.types.TimestampBooleanMap; -import org.gephi.graph.api.types.TimestampByteMap; -import org.gephi.graph.api.types.TimestampCharMap; -import org.gephi.graph.api.types.TimestampDoubleMap; -import org.gephi.graph.api.types.TimestampFloatMap; -import org.gephi.graph.api.types.TimestampIntegerMap; -import org.gephi.graph.api.types.TimestampLongMap; -import org.gephi.graph.api.types.TimestampSet; -import org.gephi.graph.api.types.TimestampShortMap; -import org.gephi.graph.api.types.TimestampStringMap; -import org.gephi.graph.api.types.TimestampMap; import org.gephi.graph.api.Element; +import org.gephi.graph.api.Estimator; import org.gephi.graph.api.GraphView; +import org.gephi.graph.api.Interval; import org.gephi.graph.api.TimeRepresentation; import org.gephi.graph.api.types.IntervalBooleanMap; import org.gephi.graph.api.types.IntervalByteMap; @@ -45,24 +35,35 @@ import org.gephi.graph.api.types.IntervalIntegerMap; import org.gephi.graph.api.types.IntervalLongMap; import org.gephi.graph.api.types.IntervalMap; -import org.gephi.graph.api.types.IntervalSet; import org.gephi.graph.api.types.IntervalShortMap; import org.gephi.graph.api.types.IntervalStringMap; import org.gephi.graph.api.types.TimeMap; import org.gephi.graph.api.types.TimeSet; +import org.gephi.graph.api.types.TimestampBooleanMap; +import org.gephi.graph.api.types.TimestampByteMap; +import org.gephi.graph.api.types.TimestampCharMap; +import org.gephi.graph.api.types.TimestampDoubleMap; +import org.gephi.graph.api.types.TimestampFloatMap; +import org.gephi.graph.api.types.TimestampIntegerMap; +import org.gephi.graph.api.types.TimestampLongMap; +import org.gephi.graph.api.types.TimestampMap; +import org.gephi.graph.api.types.TimestampShortMap; +import org.gephi.graph.api.types.TimestampStringMap; public abstract class ElementImpl implements Element { // Reference to store protected final GraphStore graphStore; // Attributes - protected Object[] attributes; + protected final AttributesImpl attributes; public ElementImpl(Object id, GraphStore graphStore) { if (id == null) { throw new NullPointerException(); } this.graphStore = graphStore; + this.attributes = new AttributesImpl(getColumnStore()); + this.attributes.setId(id); } abstract ColumnStore getColumnStore(); @@ -71,17 +72,23 @@ public ElementImpl(Object id, GraphStore graphStore) { abstract boolean isValid(); + abstract DefaultColumnsImpl.TableDefaultColumns getDefaultColumns(); + @Override public Object getId() { - return attributes[GraphStoreConfiguration.ELEMENT_ID_INDEX]; + return attributes.getId(); } @Override public String getLabel() { - if (GraphStoreConfiguration.ENABLE_ELEMENT_LABEL && attributes.length > GraphStoreConfiguration.ELEMENT_LABEL_INDEX) { - return (String) attributes[GraphStoreConfiguration.ELEMENT_LABEL_INDEX]; + return attributes.getLabel(); + } + + @Override + public void setLabel(String label) { + if (GraphStoreConfiguration.ENABLE_ELEMENT_LABEL) { + setAttribute(getDefaultColumns().label, label); } - return null; } @Override @@ -93,18 +100,7 @@ public Object getAttribute(String key) { public Object getAttribute(Column column) { checkColumn(column); - int index = column.getIndex(); - Object res = null; - synchronized (this) { - if (index < attributes.length) { - res = attributes[index]; - } - } - - if (res == null) { - return column.getDefaultValue(); - } - return res; + return attributes.getAttribute(column); } @Override @@ -116,7 +112,9 @@ public Object getAttribute(String key, double timestamp) { public Object getAttribute(Column column, double timestamp) { checkTimeRepresentationTimestamp(); checkDouble(timestamp); - return getTimeAttribute(column, timestamp); + checkColumn(column); + checkColumnDynamic(column); + return attributes.getAttribute(column, timestamp, null); } @Override @@ -127,24 +125,9 @@ public Object getAttribute(String key, Interval interval) { @Override public Object getAttribute(Column column, Interval interval) { checkTimeRepresentationInterval(); - return getTimeAttribute(column, interval); - } - - private Object getTimeAttribute(Column column, Object timeObject) { checkColumn(column); checkColumnDynamic(column); - - int index = column.getIndex(); - synchronized (this) { - TimeMap dynamicValue = null; - if (index < attributes.length) { - dynamicValue = (TimeMap) attributes[index]; - } - if (dynamicValue != null) { - return dynamicValue.get(timeObject, column.getDefaultValue()); - } - } - return null; + return attributes.getAttribute(column, interval, null); } @Override @@ -154,36 +137,25 @@ public Object getAttribute(String key, GraphView view) { @Override public Object getAttribute(Column column, GraphView view) { + Estimator estimator = column.getEstimator(); + return getAttribute(column, view, estimator); + } + + protected Object getAttribute(Column column, GraphView view, Estimator estimator) { checkColumn(column); if (!column.isDynamic()) { return getAttribute(column); } else { Interval interval = view.getTimeInterval(); - checkViewExist((GraphView) view); - - int index = column.getIndex(); - synchronized (this) { - TimeMap dynamicValue = null; - if (index < attributes.length) { - dynamicValue = (TimeMap) attributes[index]; - } - if (dynamicValue != null && !dynamicValue.isEmpty()) { - Estimator estimator = column.getEstimator(); - if (estimator == null) { - estimator = GraphStoreConfiguration.DEFAULT_ESTIMATOR; - } - return dynamicValue.get(interval, estimator); - } - } + checkViewExist(view); + return attributes.getAttribute(column, interval, estimator); } - - return null; } @Override public Object[] getAttributes() { - return attributes; + return attributes.getBackingArray(); } @Override @@ -206,37 +178,9 @@ public Object removeAttribute(Column column) { checkColumn(column); checkReadOnlyColumn(column); - int index = column.getIndex(); - Object oldValue = null; - synchronized (this) { - if (index >= attributes.length) { - Object[] newArray = new Object[index + 1]; - System.arraycopy(attributes, 0, newArray, 0, attributes.length); - attributes = newArray; - } else { - oldValue = attributes[index]; - } - - attributes[index] = null; - } + Object oldValue = attributes.setAttribute(column, column.getDefaultValue()); + updateIndex(column, oldValue, column.getDefaultValue()); - if (isValid()) { - ColumnStore columnStore = getColumnStore(); - ColumnImpl columnImpl = (ColumnImpl) column; - if (columnImpl.isDynamic() && oldValue != null) { - TimeIndexStore timeIndexStore = getTimeIndexStore(); - if (timeIndexStore != null) { - if (TimeMap.class.isAssignableFrom(columnImpl.getTypeClass())) { - timeIndexStore.remove((TimeMap) oldValue); - } else if (TimeSet.class.isAssignableFrom(columnImpl.getTypeClass())) { - timeIndexStore.remove((TimeSet) oldValue); - } - } - } else if (column.isIndexed() && columnStore != null && isValid()) { - columnStore.indexStore.set(column, oldValue, column.getDefaultValue(), this); - } - columnImpl.incrementVersion(this); - } return oldValue; } @@ -268,19 +212,10 @@ private Object removeTimeAttribute(Column column, Object timeObject) { checkColumnDynamic(column); checkReadOnlyColumn(column); - int index = column.getIndex(); - Object oldValue = null; - boolean res = false; - synchronized (this) { - TimeMap dynamicValue = (TimeMap) attributes[index]; - if (dynamicValue != null) { - oldValue = dynamicValue.get(timeObject, null); + Object oldValue = attributes.removeTimeAttribute(column, timeObject); - res = dynamicValue.remove(timeObject); - } - } - - if (res && isValid()) { + // TODO + if (oldValue != null && isValid()) { TimeIndexStore timeIndexStore = getTimeIndexStore(); if (timeIndexStore != null) { timeIndexStore.remove(timeObject); @@ -290,26 +225,6 @@ private Object removeTimeAttribute(Column column, Object timeObject) { return oldValue; } - @Override - public void setLabel(String label) { - if (GraphStoreConfiguration.ENABLE_ELEMENT_LABEL) { - int index = GraphStoreConfiguration.ELEMENT_LABEL_INDEX; - synchronized (this) { - if (index >= attributes.length) { - Object[] newArray = new Object[index + 1]; - System.arraycopy(attributes, 0, newArray, 0, attributes.length); - attributes = newArray; - } - attributes[index] = label; - } - ColumnStore columnStore = getColumnStore(); - if (columnStore != null && isValid()) { - Column col = columnStore.getColumnByIndex(index); - ((ColumnImpl) col).incrementVersion(this); - } - } - } - @Override public void setAttribute(String key, Object value) { setAttribute(checkColumnExists(key), value); @@ -323,46 +238,8 @@ public void setAttribute(Column column, Object value) { value = AttributeUtils.standardizeValue(value); checkType(column, value); - int index = column.getIndex(); - ColumnStore columnStore = getColumnStore(); - Object oldValue = null; - - synchronized (this) { - if (index >= attributes.length) { - Object[] newArray = new Object[index + 1]; - System.arraycopy(attributes, 0, newArray, 0, attributes.length); - attributes = newArray; - } else { - oldValue = attributes[index]; - } - - if (column.isDynamic() && isValid()) { - TimeIndexStore timeIndexStore = getTimeIndexStore(); - if (timeIndexStore != null) { - if (TimeMap.class.isAssignableFrom(column.getTypeClass())) { - if (oldValue != null && oldValue instanceof TimeMap) { - timeIndexStore.remove((TimeMap) oldValue); - } - if (value != null) { - timeIndexStore.add((TimeMap) value); - } - } else if (TimeSet.class.isAssignableFrom(column.getTypeClass()) && column.getIndex() == GraphStoreConfiguration.ELEMENT_TIMESET_INDEX) { - if (oldValue != null) { - timeIndexStore.remove((TimeSet) oldValue); - } - if (value != null) { - timeIndexStore.add((TimeSet) value); - } - } - } - } else if (column.isIndexed() && columnStore != null && isValid()) { - value = columnStore.indexStore.set(column, oldValue, value, this); - } - attributes[index] = value; - } - if (isValid()) { - ((ColumnImpl) column).incrementVersion(this); - } + Object oldValue = attributes.setAttribute(column, value); + updateIndex(column, oldValue, value); } @Override @@ -392,42 +269,51 @@ private void setTimeAttribute(Column column, Object value, Object timeObject) { checkColumn(column); checkColumnDynamic(column); checkReadOnlyColumn(column); - checkType(column, value); + checkDynamicType(column, value); - int index = column.getIndex(); - Object oldValue = null; - boolean res; - synchronized (this) { - if (index >= attributes.length) { - Object[] newArray = new Object[index + 1]; - System.arraycopy(attributes, 0, newArray, 0, attributes.length); - attributes = newArray; - } else { - oldValue = attributes[index]; - } + // Only a time the map did not already hold is a new reference for the time index. Passing the whole map would + // re-count every time in it, as removeTimeAttribute decrements one at a time. + boolean isNewTime = attributes.setAttribute(column, value, timeObject); + updateIndex(column, null, isNewTime ? timeObject : null); + } - TimeMap dynamicValue = null; - if (oldValue == null) { - try { - attributes[index] = dynamicValue = (TimeMap) column.getTypeClass().newInstance(); - } catch (InstantiationException | IllegalAccessException ex) { - throw new RuntimeException(ex); + private void updateIndex(Column column, Object oldValue, Object newValue) { + // Update index + if (isValid()) { + ColumnStore columnStore = getColumnStore(); + ColumnImpl columnImpl = (ColumnImpl) column; + if (columnImpl.isDynamic()) { + TimeIndexStore timeIndexStore = getTimeIndexStore(); + if (timeIndexStore != null) { + if (TimeMap.class.isAssignableFrom(columnImpl.getTypeClass())) { + if (oldValue instanceof TimeMap) { + timeIndexStore.remove((TimeMap) oldValue); + } else if (oldValue != null) { + timeIndexStore.remove(oldValue); + } + if (newValue instanceof TimeMap) { + timeIndexStore.add((TimeMap) newValue); + } else if (newValue != null) { + timeIndexStore.add(newValue); + } + } else if (TimeSet.class.isAssignableFrom(columnImpl.getTypeClass())) { + if (oldValue instanceof TimeSet) { + timeIndexStore.remove((TimeSet) oldValue, this); + } else if (oldValue != null) { + timeIndexStore.remove(oldValue, this); + } + if (newValue instanceof TimeSet) { + timeIndexStore.add((TimeSet) newValue, this); + } else if (newValue != null) { + timeIndexStore.add(newValue, this); + } + } } - } else { - dynamicValue = (TimeMap) oldValue; } - - res = dynamicValue.put(timeObject, value); - } - - if (res && isValid()) { - TimeIndexStore timeIndexStore = getTimeIndexStore(); - if (timeIndexStore != null) { - timeIndexStore.add(timeObject); + if (columnStore != null) { + columnStore.indexStore.set(column, oldValue, newValue, this); } - } - if (isValid()) { - ((ColumnImpl) column).incrementVersion(this); + columnImpl.incrementVersion(this); } } @@ -447,44 +333,10 @@ public boolean addInterval(Interval interval) { private boolean addTime(Object timeObject) { checkEnabledTimeSet(); - boolean res; - synchronized (this) { - TimeSet timeSet = getTimeSet(); - if (timeSet == null) { - TimeRepresentation timeRepresentation = getTimeRepresentation(); - switch (timeRepresentation) { - case INTERVAL: - timeSet = new IntervalSet(); - break; - case TIMESTAMP: - timeSet = new TimestampSet(); - break; - default: - throw new RuntimeException("Unrecognized time representation"); - } - int index = GraphStoreConfiguration.ELEMENT_TIMESET_INDEX; - if (index >= attributes.length) { - Object[] newArray = new Object[index + 1]; - System.arraycopy(attributes, 0, newArray, 0, attributes.length); - attributes = newArray; - } - attributes[index] = timeSet; - } - res = timeSet.add(timeObject); - } - - if (res && isValid()) { - TimeIndexStore timeIndexStore = getTimeIndexStore(); - if (timeIndexStore != null) { - timeIndexStore.add(timeObject, this); - } - ColumnStore columnStore = getColumnStore(); - if (columnStore != null) { - Column column = columnStore.getColumnByIndex(GraphStoreConfiguration.ELEMENT_TIMESET_INDEX); - ((ColumnImpl) column).incrementVersion(this); - } + boolean res = attributes.addTime(timeObject); + if (res) { + updateIndex(getDefaultColumns().timeset, null, timeObject); } - return res; } @@ -504,33 +356,18 @@ public boolean removeInterval(Interval interval) { private boolean removeTime(Object timeObject) { checkEnabledTimeSet(); - boolean res = false; - synchronized (this) { - TimeSet timeSet = getTimeSet(); - if (timeSet != null) { - res = timeSet.remove(timeObject); - } + boolean res = attributes.removeTime(timeObject); + if (res) { + updateIndex(getDefaultColumns().timeset, timeObject, null); } - - if (res && isValid()) { - TimeIndexStore timeIndexStore = getTimeIndexStore(); - if (timeIndexStore != null) { - timeIndexStore.remove(timeObject, this); - } - ColumnStore columnStore = getColumnStore(); - if (columnStore != null) { - Column column = columnStore.getColumnByIndex(GraphStoreConfiguration.ELEMENT_TIMESET_INDEX); - ((ColumnImpl) column).incrementVersion(this); - } - } - return res; } @Override public double[] getTimestamps() { checkTimeRepresentationTimestamp(); - Object res = getTimeSetArray(); + checkEnabledTimeSet(); + Object res = attributes.getTimeSetArray(); if (res == null) { return new double[0]; } @@ -540,20 +377,23 @@ public double[] getTimestamps() { @Override public Interval[] getIntervals() { checkTimeRepresentationInterval(); - Object res = getTimeSetArray(); + checkEnabledTimeSet(); + Object res = attributes.getTimeSetArray(); if (res == null) { return new Interval[0]; } return (Interval[]) res; } - private Object getTimeSetArray() { + @Override + public Interval getTimeBounds() { checkEnabledTimeSet(); - - synchronized (this) { - TimeSet timeSet = getTimeSet(); - if (timeSet != null) { - return timeSet.toPrimitiveArray(); + TimeSet timeSet = attributes.getTimeSet(); + if (timeSet != null) { + Double min = timeSet.getMinDouble(); + Double max = timeSet.getMaxDouble(); + if (min != null) { + return new Interval(min, max); } } return null; @@ -562,58 +402,27 @@ private Object getTimeSetArray() { @Override public boolean hasTimestamp(double timestamp) { checkTimeRepresentationTimestamp(); - return hasTime(timestamp); + checkEnabledTimeSet(); + return attributes.hasTime(timestamp); } @Override public boolean hasInterval(Interval interval) { checkTimeRepresentationInterval(); - return hasTime(interval); - } - - private boolean hasTime(Object timeObject) { checkEnabledTimeSet(); - - synchronized (this) { - TimeSet timeSet = getTimeSet(); - if (timeSet != null) { - return timeSet.contains(timeObject); - } - } - return false; + return attributes.hasTime(interval); } @Override public Iterable getAttributes(Column column) { checkColumn(column); - checkColumnDynamic(column); + checkColumnDynamicAttribute(column); - int index = column.getIndex(); - TimeMap dynamicValue = null; - synchronized (this) { - if (index < attributes.length) { - dynamicValue = (TimeMap) attributes[index]; - } - if (dynamicValue != null) { - Object[] values = dynamicValue.toValuesArray(); - if (dynamicValue instanceof TimestampMap) { - return new TimeAttributeIterable(((TimestampMap) dynamicValue).getTimestamps(), values); - } else if (dynamicValue instanceof IntervalMap) { - return new TimeAttributeIterable(((IntervalMap) dynamicValue).toKeysArray(), values); - } - } - - } - return TimeAttributeIterable.EMPTY_ITERABLE; - } - - private TimeSet getTimeSet() { - if (GraphStoreConfiguration.ENABLE_ELEMENT_TIME_SET && GraphStoreConfiguration.ELEMENT_TIMESET_INDEX < attributes.length) { - return (TimeSet) attributes[GraphStoreConfiguration.ELEMENT_TIMESET_INDEX]; - } - return null; + return attributes.getAttributes(column); } + // Called when elements are added + // TODO protected void indexAttributes() { synchronized (this) { ColumnStore columnStore = getColumnStore(); @@ -631,24 +440,31 @@ protected void indexAttributes() { @Override public void clearAttributes() { synchronized (this) { - if (isValid()) { - ColumnStore columnStore = getColumnStore(); - if (columnStore != null) { - columnStore.indexStore.clear(this); - } - TimeIndexStore timeIndexStore = getTimeIndexStore(); - if (timeIndexStore != null) { - timeIndexStore.clear(this); + ColumnStore columnStore = getColumnStore(); + if (columnStore != null) { + final int length = columnStore.length; + final ColumnImpl[] cols = columnStore.columns; + for (int i = 0; i < length; i++) { + Column c = cols[i]; + if (!c.isProperty() && !c.isReadOnly()) { + removeAttribute(c); + } } } - TimeSet timeSet = getTimeSet(); - if (timeSet != null) { - timeSet.clear(); + } + } + + protected void destroyAttributes() { + synchronized (this) { + ColumnStore columnStore = getColumnStore(); + if (columnStore != null) { + columnStore.indexStore.clear(this); } - Object[] newAttributes = new Object[GraphStoreConfiguration.ELEMENT_ID_INDEX + 1]; - newAttributes[GraphStoreConfiguration.ELEMENT_ID_INDEX] = attributes[GraphStoreConfiguration.ELEMENT_ID_INDEX]; - attributes = newAttributes; + TimeIndexStore timeIndexStore = getTimeIndexStore(); + if (timeIndexStore != null) { + timeIndexStore.clear(this); + } } } @@ -668,10 +484,15 @@ public boolean equals(Object obj) { return false; } final ElementImpl other = (ElementImpl) obj; - if (!this.getId().equals(other.getId())) { - return false; + return this.getId().equals(other.getId()); + } + + protected Estimator getEstimator(Column column) { + Estimator estimator = column.getEstimator(); + if (estimator == null) { + return GraphStoreConfiguration.DEFAULT_ESTIMATOR; } - return true; + return estimator; } protected GraphStore getGraphStore() { @@ -680,7 +501,8 @@ protected GraphStore getGraphStore() { protected void checkTimeRepresentationTimestamp() { if (!getTimeRepresentation().equals(TimeRepresentation.TIMESTAMP)) { - throw new RuntimeException("Can't use timestamps as the configuration is set to " + getTimeRepresentation()); + throw new RuntimeException( + "Can't use timestamps as the configuration is set to " + getTimeRepresentation()); } } @@ -727,58 +549,75 @@ void checkReadOnlyColumn(Column column) { } void checkColumnDynamic(Column column) { - if (!((ColumnImpl) column).isDynamic()) { + if (!column.isDynamic()) { throw new IllegalArgumentException("The column is not dynamic"); } } - void checkType(Column column, Object value) { + void checkColumnDynamicAttribute(Column column) { + if (!column.isDynamicAttribute()) { + throw new IllegalArgumentException("The column is not a dynamic attribute"); + } + } + + void checkDynamicType(Column column, Object value) { if (value != null) { Class typeClass = column.getTypeClass(); if (TimestampMap.class.isAssignableFrom(typeClass)) { - if ((value instanceof Double && (!typeClass.equals(TimestampDoubleMap.class))) || (value instanceof Float && !typeClass - .equals(TimestampFloatMap.class)) || (value instanceof Boolean && !typeClass - .equals(TimestampBooleanMap.class)) || (value instanceof Integer && !typeClass - .equals(TimestampIntegerMap.class)) || (value instanceof Long && !typeClass - .equals(TimestampLongMap.class)) || (value instanceof Short && !typeClass - .equals(TimestampShortMap.class)) || (value instanceof Byte && !typeClass - .equals(TimestampByteMap.class)) || (value instanceof String && !typeClass - .equals(TimestampStringMap.class)) || (value instanceof Character && !typeClass - .equals(TimestampCharMap.class))) { + checkTimeRepresentationTimestamp(); + if ((value instanceof Double && (!typeClass + .equals(TimestampDoubleMap.class))) || (value instanceof Float && !typeClass + .equals(TimestampFloatMap.class)) || (value instanceof Boolean && !typeClass + .equals(TimestampBooleanMap.class)) || (value instanceof Integer && !typeClass + .equals(TimestampIntegerMap.class)) || (value instanceof Long && !typeClass + .equals(TimestampLongMap.class)) || (value instanceof Short && !typeClass + .equals(TimestampShortMap.class)) || (value instanceof Byte && !typeClass + .equals(TimestampByteMap.class)) || (value instanceof String && !typeClass + .equals(TimestampStringMap.class)) || (value instanceof Character && !typeClass + .equals(TimestampCharMap.class))) { throw new IllegalArgumentException( "The object class does not match with the dynamic type (" + typeClass.getName() + ")"); } } else if (IntervalMap.class.isAssignableFrom(typeClass)) { - if ((value instanceof Double && (!typeClass.equals(IntervalDoubleMap.class))) || (value instanceof Float && !typeClass - .equals(IntervalFloatMap.class)) || (value instanceof Boolean && !typeClass - .equals(IntervalBooleanMap.class)) || (value instanceof Integer && !typeClass - .equals(IntervalIntegerMap.class)) || (value instanceof Long && !typeClass - .equals(IntervalLongMap.class)) || (value instanceof Short && !typeClass - .equals(IntervalShortMap.class)) || (value instanceof Byte && !typeClass - .equals(IntervalByteMap.class)) || (value instanceof String && !typeClass - .equals(IntervalStringMap.class)) || (value instanceof Character && !typeClass - .equals(IntervalCharMap.class))) { + checkTimeRepresentationInterval(); + if ((value instanceof Double && (!typeClass + .equals(IntervalDoubleMap.class))) || (value instanceof Float && !typeClass + .equals(IntervalFloatMap.class)) || (value instanceof Boolean && !typeClass + .equals(IntervalBooleanMap.class)) || (value instanceof Integer && !typeClass + .equals(IntervalIntegerMap.class)) || (value instanceof Long && !typeClass + .equals(IntervalLongMap.class)) || (value instanceof Short && !typeClass + .equals(IntervalShortMap.class)) || (value instanceof Byte && !typeClass + .equals(IntervalByteMap.class)) || (value instanceof String && !typeClass + .equals(IntervalStringMap.class)) || (value instanceof Character && !typeClass + .equals(IntervalCharMap.class))) { throw new IllegalArgumentException( "The object class does not match with the dynamic type (" + typeClass.getName() + ")"); } - } else if (List.class.isAssignableFrom(typeClass)) { + } + } + } + + void checkType(Column column, Object value) { + if (value != null) { + Class typeClass = column.getTypeClass(); + if (List.class.isAssignableFrom(typeClass)) { if (!(value instanceof List)) { - throw new IllegalArgumentException( - "The object class does not match with the list type (" + typeClass.getName() + ")"); + throw new IllegalArgumentException("The object class " + value.getClass() + .getName() + " does not match with the list type (" + typeClass.getName() + ")"); } } else if (Set.class.isAssignableFrom(typeClass)) { if (!(value instanceof Set)) { - throw new IllegalArgumentException( - "The object class does not match with the set type (" + typeClass.getName() + ")"); + throw new IllegalArgumentException("The object class " + value.getClass() + .getName() + " does not match with the set type (" + typeClass.getName() + ")"); } } else if (Map.class.isAssignableFrom(typeClass)) { if (!(value instanceof Map)) { - throw new IllegalArgumentException( - "The object class does not match with the map type (" + typeClass.getName() + ")"); + throw new IllegalArgumentException("The object class " + value.getClass() + .getName() + " does not match with the map type (" + typeClass.getName() + ")"); } } else if (!value.getClass().equals(typeClass)) { - throw new IllegalArgumentException( - "The object class does not match with the column type (" + typeClass.getName() + ")"); + throw new IllegalArgumentException("The object class " + value.getClass() + .getName() + " does not match with the column type (" + typeClass.getName() + ")"); } } } diff --git a/src/main/java/org/gephi/graph/impl/ElementIterableWrapper.java b/src/main/java/org/gephi/graph/impl/ElementIterableWrapper.java new file mode 100644 index 00000000..3f2a7fcd --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/ElementIterableWrapper.java @@ -0,0 +1,104 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.gephi.graph.api.Element; +import org.gephi.graph.api.ElementIterable; + +public abstract class ElementIterableWrapper implements ElementIterable { + + protected final Supplier> iteratorSupplier; + protected final Supplier> spliteratorSupplier; + protected final GraphLockImpl lock; + protected final boolean parallelPossible; + + public ElementIterableWrapper(Supplier> iteratorSupplier, GraphLockImpl lock) { + this.iteratorSupplier = iteratorSupplier; + this.spliteratorSupplier = () -> Spliterators + .spliteratorUnknownSize(iteratorSupplier.get(), Spliterator.ORDERED | Spliterator.NONNULL); + this.lock = lock; + this.parallelPossible = false; + } + + public ElementIterableWrapper(Supplier> iteratorSupplier, Supplier> spliteratorSupplier, GraphLockImpl lock) { + this.iteratorSupplier = iteratorSupplier; + this.spliteratorSupplier = spliteratorSupplier; + this.lock = lock; + this.parallelPossible = true; + } + + @Override + public Iterator iterator() { + return iteratorSupplier.get(); + } + + @Override + public Spliterator spliterator() { + return spliteratorSupplier.get(); + } + + @Override + public Stream parallelStream() { + if (!parallelPossible) { + throw new UnsupportedOperationException("Parallel stream not supported for this operation."); + } + return ElementIterable.super.parallelStream(); + } + + public abstract T[] toArray(); + + @Override + public Collection toCollection() { + if (parallelPossible && lock != null) { + lock.readLock(); + try { + return StreamSupport.stream(spliterator(), true).collect(Collectors.toList()); + } finally { + lock.readUnlock(); + } + } + return StreamSupport.stream(spliterator(), parallelPossible).collect(Collectors.toList()); + } + + @Override + public Set toSet() { + if (parallelPossible && lock != null) { + lock.readLock(); + try { + return StreamSupport.stream(spliterator(), true).collect(Collectors.toSet()); + } finally { + lock.readUnlock(); + } + } + return StreamSupport.stream(spliterator(), parallelPossible).collect(Collectors.toSet()); + } + + @Override + public void doBreak() { + if (lock != null) { + lock.readUnlock(); + } + } +} diff --git a/store/src/main/java/org/gephi/graph/impl/FormattingAndParsingUtils.java b/src/main/java/org/gephi/graph/impl/FormattingAndParsingUtils.java similarity index 75% rename from store/src/main/java/org/gephi/graph/impl/FormattingAndParsingUtils.java rename to src/main/java/org/gephi/graph/impl/FormattingAndParsingUtils.java index c3f92bf1..4c434f5b 100644 --- a/store/src/main/java/org/gephi/graph/impl/FormattingAndParsingUtils.java +++ b/src/main/java/org/gephi/graph/impl/FormattingAndParsingUtils.java @@ -20,12 +20,12 @@ import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.ZoneId; +import java.time.format.DateTimeParseException; import org.gephi.graph.api.AttributeUtils; -import org.joda.time.DateTimeZone; /** - * Utils for formatting and parsing special data types (dynamic intervals, - * timestamps and arrays). + * Utils for formatting and parsing special data types (dynamic intervals, timestamps and arrays). * * @author Eduardo Ramos */ @@ -44,14 +44,15 @@ public final class FormattingAndParsingUtils { public static final String INFINITY = "Infinity"; /** - * Parses an ISO date with or without time or a timestamp (in milliseconds). - * Returns the date or timestamp converted to a timestamp in milliseconds. + * Parses an ISO date with or without time or a timestamp (in milliseconds). Returns the date or timestamp converted + * to a timestamp in milliseconds. * * @param timeStr Date or timestamp string - * @param timeZone Time zone to use or null to use default time zone (UTC) + * @param zoneId Time zone to use or null to use default time zone (UTC) * @return Timestamp + * @throws DateTimeParseException if the time cannot be parsed */ - public static double parseDateTimeOrTimestamp(String timeStr, DateTimeZone timeZone) { + public static double parseDateTimeOrTimestamp(String timeStr, ZoneId zoneId) throws DateTimeParseException { double value; try { // Try first to parse as a single double: @@ -59,22 +60,22 @@ public static double parseDateTimeOrTimestamp(String timeStr, DateTimeZone timeZ if (Double.isNaN(value)) { throw new IllegalArgumentException("NaN is not allowed as an interval bound"); } - } catch (Exception ex) { - value = AttributeUtils.parseDateTime(timeStr, timeZone); + } catch (NumberFormatException ex) { + value = AttributeUtils.parseDateTime(timeStr, zoneId); } return value; } /** - * Parses an ISO date with or without time or a timestamp (in milliseconds). - * Returns the date or timestamp converted to a timestamp in milliseconds. - * Default time zone is used (UTC). + * Parses an ISO date with or without time or a timestamp (in milliseconds). Returns the date or timestamp converted + * to a timestamp in milliseconds. Default time zone is used (UTC). * * @param timeStr Date or timestamp string * @return Timestamp + * @throws DateTimeParseException if the time cannot be parsed */ - public static double parseDateTimeOrTimestamp(String timeStr) { + public static double parseDateTimeOrTimestamp(String timeStr) throws DateTimeParseException { return parseDateTimeOrTimestamp(timeStr, null); } @@ -125,21 +126,40 @@ protected static String parseLiteral(StringReader reader, char quote) throws IOE } /** - * Parses a value until end is detected either by a comma or a bounds - * closing character. + * Parses a value until end is detected either by a comma or a bounds closing character. Both {@code )} and + * {@code ]} are treated as a closing bound, since this is used for parsing interval and timestamp bounds which can + * be opened/closed with either bracket type. * * @param reader Input reader * @return Parsed value * @throws IOException Unexpected read error */ protected static String parseValue(StringReader reader) throws IOException { + return parseValue(reader, true); + } + + /** + * Parses a value until end is detected either by a comma or a bounds closing character. + * + * @param reader Input reader + * @param roundBracketIsClosingBound Whether {@code )} should be treated as a closing bound character in addition to + * {@code ]}. This should be disabled for grammars, such as plain arrays, where {@code (} and {@code )} have + * no structural meaning and can be part of an unquoted value. + * @return Parsed value + * @throws IOException Unexpected read error + */ + protected static String parseValue(StringReader reader, boolean roundBracketIsClosingBound) throws IOException { StringBuilder sb = new StringBuilder(); int r; char c; while ((r = reader.read()) != -1) { c = (char) r; + if (roundBracketIsClosingBound && c == RIGHT_BOUND_BRACKET) { + reader.skip(-1);// Go backwards 1 position, for detecting end + // of bounds + return sb.toString().trim(); + } switch (c) { - case RIGHT_BOUND_BRACKET: case RIGHT_BOUND_SQUARE_BRACKET: reader.skip(-1);// Go backwards 1 position, for detecting // end of bounds @@ -154,9 +174,8 @@ protected static String parseValue(StringReader reader) throws IOException { } /** - * Converts a string parsed with {@link #parseValue(java.io.StringReader)} - * to the target type, taking into account dynamic parsing quirks such as - * numbers with/without decimals and infinity values. + * Converts a string parsed with {@link #parseValue(java.io.StringReader)} to the target type, taking into account + * dynamic parsing quirks such as numbers with/without decimals and infinity values. * * @param Target type * @param typeClass Target type class @@ -165,27 +184,25 @@ protected static String parseValue(StringReader reader) throws IOException { */ protected static T convertValue(Class typeClass, String valString) { Object value; - if (typeClass.equals(Byte.class) || typeClass.equals(byte.class) || typeClass.equals(Short.class) || typeClass - .equals(short.class) || typeClass.equals(Integer.class) || typeClass.equals(int.class) || typeClass - .equals(Long.class) || typeClass.equals(long.class) || typeClass.equals(BigInteger.class)) { + if (typeClass.equals(String.class)) { + value = valString; + } else if (typeClass.equals(Byte.class) || typeClass.equals(byte.class) || typeClass + .equals(Short.class) || typeClass.equals(short.class) || typeClass.equals(Integer.class) || typeClass + .equals(int.class) || typeClass.equals(Long.class) || typeClass + .equals(long.class) || typeClass.equals(BigInteger.class)) { value = parseNumberWithoutDecimals((Class) typeClass, valString); - } else if (typeClass.equals(Float.class) || typeClass.equals(float.class) || typeClass.equals(Double.class) || typeClass - .equals(double.class) || typeClass.equals(BigDecimal.class)) { + } else if (typeClass.equals(Float.class) || typeClass.equals(float.class) || typeClass + .equals(Double.class) || typeClass.equals(double.class) || typeClass.equals(BigDecimal.class)) { value = parseNumberWithDecimals((Class) typeClass, valString); } else { value = AttributeUtils.parse(valString, typeClass); } - if (value == null) { - throw new IllegalArgumentException("Invalid value for type: " + valString); - } - return (T) value; } /** - * Method for allowing inputs such as "infinity" when parsing decimal - * numbers + * Method for allowing inputs such as "infinity" when parsing decimal numbers * * @param value Input String * @return Input String with fixed "Infinity" syntax if necessary. @@ -215,9 +232,8 @@ private static T parseNumberWithDecimals(Class typeClass, } /** - * Removes anything after the dot of decimal numbers in a string when - * necessary. Used for trying to parse decimal numbers as not decimal. For - * example BigDecimal to BigInteger. + * Removes anything after the dot of decimal numbers in a string when necessary. Used for trying to parse decimal + * numbers as not decimal. For example BigDecimal to BigInteger. * * @param s String to remove decimal digits * @return String without dot and decimal digits. @@ -235,8 +251,7 @@ private static String removeDecimalDigitsFromString(String s) { /** * @param value String value - * @return True if the string contains special characters for dynamic types - * intervals syntax + * @return True if the string contains special characters for dynamic types intervals syntax */ public static boolean containsDynamicSpecialCharacters(String value) { for (char c : DYNAMIC_SPECIAL_CHARACTERS) { @@ -288,8 +303,7 @@ public static String printArray(Object arr) { /** * @param value String value - * @return True if the string contains special characters for arrays - * intervals syntax + * @return True if the string contains special characters for arrays intervals syntax */ private static boolean containsArraySpecialCharacters(String value) { for (char c : ARRAY_SPECIAL_CHARACTERS) { diff --git a/store/src/main/java/org/gephi/graph/impl/GraphAttributesImpl.java b/src/main/java/org/gephi/graph/impl/GraphAttributesImpl.java similarity index 84% rename from store/src/main/java/org/gephi/graph/impl/GraphAttributesImpl.java rename to src/main/java/org/gephi/graph/impl/GraphAttributesImpl.java index a98fcc55..a23fa322 100644 --- a/store/src/main/java/org/gephi/graph/impl/GraphAttributesImpl.java +++ b/src/main/java/org/gephi/graph/impl/GraphAttributesImpl.java @@ -15,24 +15,28 @@ */ package org.gephi.graph.impl; -import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.TreeMap; import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Interval; import org.gephi.graph.api.types.TimeMap; -import org.gephi.graph.api.types.TimestampMap; import org.gephi.graph.impl.utils.MapDeepEquals; public class GraphAttributesImpl { - protected final Map attributes = new HashMap<>(); + // A TreeMap is used so the iteration order is canonical (sorted by key). + // This makes serialization a pure function of the content rather than of + // the insertion history, which is required for byte-pinned fixtures. + protected final Map attributes = new TreeMap<>(); public synchronized Set getKeys() { return attributes.keySet(); } public synchronized void setValue(String key, Object value) { + Objects.requireNonNull(key, "key"); if (value != null) { checkSupportedTypes(value.getClass()); } @@ -40,18 +44,22 @@ public synchronized void setValue(String key, Object value) { } public synchronized void removeValue(String key) { + Objects.requireNonNull(key, "key"); attributes.remove(key); } public synchronized Object getValue(String key) { + Objects.requireNonNull(key, "key"); return attributes.get(key); } public synchronized Object getValue(String key, double timestamp) { + Objects.requireNonNull(key, "key"); return getValueInternal(key, timestamp); } public synchronized Object getValue(String key, Interval interval) { + Objects.requireNonNull(key, "key"); return getValueInternal(key, interval); } @@ -64,11 +72,13 @@ private Object getValueInternal(String key, Object timeObj) { } public synchronized void removeValue(String key, double timestamp) { + Objects.requireNonNull(key, "key"); removeValueInternal(key, timestamp); } public synchronized void removeValue(String key, Interval interval) { + Objects.requireNonNull(key, "key"); removeValueInternal(key, interval); } @@ -84,10 +94,12 @@ private void removeValueInternal(String key, Object timeObj) { } public synchronized void setValue(String key, Object value, double timestamp) { + Objects.requireNonNull(key, "key"); setValueInternal(key, value, timestamp); } public synchronized void setValue(String key, Object value, Interval interval) { + Objects.requireNonNull(key, "key"); setValueInternal(key, value, interval); } @@ -100,9 +112,8 @@ private void setValueInternal(String key, Object value, Object timeObj) { valueSet = (TimeMap) attributes.get(key); if (!value.getClass().equals(valueSet.getTypeClass())) { - throw new IllegalArgumentException( - "The value type " + value.getClass().getName() + " doesn't match with the expected type " + valueSet - .getTypeClass().getName()); + throw new IllegalArgumentException("The value type " + value.getClass() + .getName() + " doesn't match with the expected type " + valueSet.getTypeClass().getName()); } } else { if (timeObj instanceof Interval) { diff --git a/store/src/main/java/org/gephi/graph/impl/GraphBridgeImpl.java b/src/main/java/org/gephi/graph/impl/GraphBridgeImpl.java similarity index 70% rename from store/src/main/java/org/gephi/graph/impl/GraphBridgeImpl.java rename to src/main/java/org/gephi/graph/impl/GraphBridgeImpl.java index f098535f..9766eb0d 100644 --- a/store/src/main/java/org/gephi/graph/impl/GraphBridgeImpl.java +++ b/src/main/java/org/gephi/graph/impl/GraphBridgeImpl.java @@ -20,8 +20,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Column; -import org.gephi.graph.api.Configuration; import org.gephi.graph.api.Edge; import org.gephi.graph.api.Element; import org.gephi.graph.api.GraphBridge; @@ -97,11 +97,17 @@ public void copyNodes(Node[] nodes) { if (store.getNode(node.getId()) == null) { Node nodeCopy = factory.newNode(node.getId()); - // Properties - copyNodeProperties(node, nodeCopy); + // Label + copyLabel(node, nodeCopy); + + // Time set + copyTimeSet(node, nodeCopy); - // Text properties - copyTextProperties(node.getTextProperties(), nodeCopy.getTextProperties()); + // Properties + if (store.configuration.isEnableNodeProperties()) { + copyNodeProperties(node, nodeCopy); + copyTextProperties(node.getTextProperties(), nodeCopy.getTextProperties()); + } // Attributes copyAttributes(sourceStore.nodeTable, nodeTable, node, nodeCopy); @@ -119,14 +125,20 @@ public void copyNodes(Node[] nodes) { Edge edgeCopy = factory.newEdge(edge.getId(), source, target, edge.getType(), 0.0, edge.isDirected()); + // Label + copyLabel(edge, edgeCopy); + + // Time set + copyTimeSet(edge, edgeCopy); + // Weight copyEdgeWeight(edge, edgeCopy); // Properties - copyEdgeProperties(edge, edgeCopy); - - // Text properties - copyTextProperties(edge.getTextProperties(), edgeCopy.getTextProperties()); + if (store.configuration.isEnableEdgeProperties()) { + copyEdgeProperties(edge, edgeCopy); + copyTextProperties(edge.getTextProperties(), edgeCopy.getTextProperties()); + } // Attributes copyAttributes(sourceStore.edgeTable, edgeTable, edge, edgeCopy); @@ -158,19 +170,29 @@ private void copyNodeProperties(Node node, Node nodeCopy) { nodeCopy.setPosition(node.x(), node.y(), node.z()); nodeCopy.setColor(node.getColor()); nodeCopy.setFixed(node.isFixed()); - nodeCopy.setLabel(node.getLabel()); nodeCopy.setSize(node.size()); } + private void copyLabel(Element element, Element elementCopy) { + elementCopy.setLabel(element.getLabel()); + } + private void copyEdgeProperties(Edge edge, Edge edgeCopy) { edgeCopy.setColor(edge.getColor()); - edgeCopy.setLabel(edge.getLabel()); } private void copyTextProperties(TextProperties text, TextProperties textCopy) { textCopy.setColor(text.getColor()); textCopy.setSize(text.getSize()); textCopy.setVisible(text.isVisible()); + textCopy.setText(text.getText()); + textCopy.setDimensions(text.getWidth(), text.getHeight()); + } + + private void copyTimeSet(Element element, Element elementCopy) { + Column sourceColumn = element.getTable().getColumn(GraphStoreConfiguration.ELEMENT_TIMESET_INDEX); + Column destColumn = elementCopy.getTable().getColumn(GraphStoreConfiguration.ELEMENT_TIMESET_INDEX); + elementCopy.setAttribute(destColumn, AttributeUtils.copy(element.getAttribute(sourceColumn))); } private void copyColumns(TableImpl sourceTable, TableImpl destTable) { @@ -183,26 +205,10 @@ private void copyColumns(TableImpl sourceTable, TableImpl destTable) { } private void copyAttributes(TableImpl sourceTable, TableImpl destTable, Element element, Element elementCopy) { - TimeRepresentation tr = sourceTable.store.configuration.getTimeRepresentation(); for (Column col : sourceTable.toArray()) { if (!col.isProperty()) { Column colCopy = destTable.getColumn(col.getId()); - if (col.isDynamic() && tr.equals(TimeRepresentation.TIMESTAMP)) { - for (Map.Entry entry : element.getAttributes(col)) { - Double key = entry.getKey(); - Object value = entry.getValue(); - elementCopy.setAttribute(colCopy, value, key); - } - } else if (col.isDynamic() && tr.equals(TimeRepresentation.INTERVAL)) { - for (Map.Entry entry : element.getAttributes(col)) { - Interval key = entry.getKey(); - Object value = entry.getValue(); - elementCopy.setAttribute(colCopy, value, key); - } - } else { - Object attribute = element.getAttribute(col); - elementCopy.setAttribute(colCopy, attribute); - } + elementCopy.setAttribute(colCopy, AttributeUtils.copy(element.getAttribute(col))); } } } @@ -233,10 +239,45 @@ private void verifyElement(ElementImpl elementImpl) { private void verifyCompatibility(GraphStore sourceStore) { // Verify configuration - Configuration destConfig = store.configuration; - Configuration sourceConfig = sourceStore.configuration; - if (!destConfig.equals(sourceConfig)) { - throw new RuntimeException("The configurations don't match"); + ConfigurationImpl destConfig = store.configuration; + ConfigurationImpl sourceConfig = sourceStore.configuration; + + // Time representation + if (!destConfig.getTimeRepresentation().equals(sourceConfig.getTimeRepresentation())) { + throw new RuntimeException("The time representations doesn't match, source: " + sourceConfig + .getTimeRepresentation() + ", destination: " + destConfig.getTimeRepresentation()); + } + + // Node id type + if (!destConfig.getNodeIdType().equals(sourceConfig.getNodeIdType())) { + throw new RuntimeException("The node id type doesn't match, source: " + sourceConfig + .getNodeIdType() + ", destination: " + destConfig.getNodeIdType()); + } + + // Edge id type + if (!destConfig.getEdgeIdType().equals(sourceConfig.getEdgeIdType())) { + throw new RuntimeException("The edge id type doesn't match, source: " + sourceConfig + .getEdgeIdType() + ", destination: " + destConfig.getEdgeIdType()); + } + + // Edge weight type + if (!destConfig.getEdgeWeightType().equals(sourceConfig.getEdgeWeightType())) { + throw new RuntimeException("The edge weight type doesn't match, source: " + sourceConfig + .getEdgeWeightType() + ", destination: " + destConfig.getEdgeWeightType()); + } + + // Edge label type + if (!destConfig.getEdgeLabelType().equals(sourceConfig.getEdgeLabelType())) { + throw new RuntimeException("The edge label type doesn't match, source: " + sourceConfig + .getEdgeLabelType() + ", destination: " + destConfig.getEdgeLabelType()); + } + + // Parallel edges + if (destConfig.isEnableParallelEdgesSameType() != sourceConfig.isEnableParallelEdgesSameType()) { + throw new RuntimeException( + "The parallel edges of same type configuration doesn't match, source: " + sourceConfig + .isEnableParallelEdgesSameType() + ", destination: " + destConfig + .isEnableParallelEdgesSameType()); } // Verify node table diff --git a/store/src/main/java/org/gephi/graph/impl/GraphFactoryImpl.java b/src/main/java/org/gephi/graph/impl/GraphFactoryImpl.java similarity index 86% rename from store/src/main/java/org/gephi/graph/impl/GraphFactoryImpl.java rename to src/main/java/org/gephi/graph/impl/GraphFactoryImpl.java index c2d677ba..6567c992 100644 --- a/store/src/main/java/org/gephi/graph/impl/GraphFactoryImpl.java +++ b/src/main/java/org/gephi/graph/impl/GraphFactoryImpl.java @@ -30,17 +30,17 @@ protected enum AssignConfiguration { protected final AtomicInteger NODE_IDS = new AtomicInteger(); protected final AtomicInteger EDGE_IDS = new AtomicInteger(); // Config - protected AssignConfiguration nodeAssignConfiguration; - protected AssignConfiguration edgeAssignConfiguration; + protected final AssignConfiguration nodeAssignConfiguration; + protected final AssignConfiguration edgeAssignConfiguration; // Store protected final GraphStore store; public GraphFactoryImpl(GraphStore store) { this.store = store; - this.nodeAssignConfiguration = getAssignConfiguration(AttributeUtils.getStandardizedType(store.configuration - .getNodeIdType())); - this.edgeAssignConfiguration = getAssignConfiguration(AttributeUtils.getStandardizedType(store.configuration - .getEdgeIdType())); + this.nodeAssignConfiguration = getAssignConfiguration(AttributeUtils + .getStandardizedType(store.configuration.getNodeIdType())); + this.edgeAssignConfiguration = getAssignConfiguration(AttributeUtils + .getStandardizedType(store.configuration.getEdgeIdType())); } @Override @@ -162,23 +162,15 @@ protected void setEdgeCounter(int count) { } private static boolean isNumeric(String str) { - if (str == null) { + if (str == null || str.isEmpty()) { return false; } - char[] data = str.toCharArray(); - if (data.length <= 0 || data.length > 9) { + try { + Integer.parseInt(str); + return true; + } catch (NumberFormatException e) { return false; } - int index = 0; - if (data[0] == '-' && data.length > 1) { - index = 1; - } - for (; index < data.length; index++) { - if (data[index] < '0' || data[index] > '9') { - return false; - } - } - return true; } public int deepHashCode() { @@ -207,13 +199,6 @@ public boolean deepEquals(GraphFactoryImpl obj) { return true; } - public void resetConfiguration() { - this.nodeAssignConfiguration = getAssignConfiguration(AttributeUtils.getStandardizedType(store.configuration - .getNodeIdType())); - this.edgeAssignConfiguration = getAssignConfiguration(AttributeUtils.getStandardizedType(store.configuration - .getEdgeIdType())); - } - protected final AssignConfiguration getAssignConfiguration(Class type) { if (type.equals(Integer.class)) { return AssignConfiguration.INTEGER; diff --git a/store/src/main/java/org/gephi/graph/impl/GraphLock.java b/src/main/java/org/gephi/graph/impl/GraphLockImpl.java similarity index 60% rename from store/src/main/java/org/gephi/graph/impl/GraphLock.java rename to src/main/java/org/gephi/graph/impl/GraphLockImpl.java index dafee14d..980c6102 100644 --- a/store/src/main/java/org/gephi/graph/impl/GraphLock.java +++ b/src/main/java/org/gephi/graph/impl/GraphLockImpl.java @@ -15,30 +15,35 @@ */ package org.gephi.graph.impl; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; +import org.gephi.graph.api.GraphLock; -public class GraphLock { +public class GraphLockImpl implements GraphLock { protected final ReentrantReadWriteLock readWriteLock; protected final ReadLock readLock; protected final WriteLock writeLock; - public GraphLock() { + public GraphLockImpl() { readWriteLock = new ReentrantReadWriteLock(); readLock = readWriteLock.readLock(); writeLock = readWriteLock.writeLock(); } + @Override public void readLock() { readLock.lock(); } + @Override public void readUnlock() { readLock.unlock(); } + @Override public void readUnlockAll() { final int nReadLocks = readWriteLock.getReadHoldCount(); for (int n = 0; n < nReadLocks; n++) { @@ -46,6 +51,7 @@ public void readUnlockAll() { } } + @Override public void writeLock() { if (readWriteLock.getReadHoldCount() > 0 && !readWriteLock.isWriteLockedByCurrentThread()) { throw new IllegalMonitorStateException( @@ -54,10 +60,50 @@ public void writeLock() { writeLock.lock(); } + @Override public void writeUnlock() { writeLock.unlock(); } + @Override + public int getReadHoldCount() { + return readWriteLock.getReadHoldCount(); + } + + @Override + public int getWriteHoldCount() { + return readWriteLock.getWriteHoldCount(); + } + + @Override + public boolean tryReadLock(long timeout, TimeUnit unit) throws InterruptedException { + return readLock.tryLock(timeout, unit); + } + + @Override + public boolean tryWriteLock(long timeout, TimeUnit unit) throws InterruptedException { + if (readWriteLock.getReadHoldCount() > 0 && !readWriteLock.isWriteLockedByCurrentThread()) { + throw new IllegalMonitorStateException( + "Impossible to acquire a write lock when currently holding a read lock. Use toArray() methods on NodeIterable and EdgeIterable to avoid holding a readLock or wrap your loop with a write lock."); + } + return writeLock.tryLock(timeout, unit); + } + + @Override + public int getReadLockCount() { + return readWriteLock.getReadLockCount(); + } + + @Override + public boolean isWriteLocked() { + return readWriteLock.isWriteLocked(); + } + + @Override + public int getQueueLength() { + return readWriteLock.getQueueLength(); + } + public void checkHoldWriteLock() { if (!readWriteLock.isWriteLockedByCurrentThread()) { throw new IllegalMonitorStateException( diff --git a/store/src/main/java/org/gephi/graph/impl/GraphModelImpl.java b/src/main/java/org/gephi/graph/impl/GraphModelImpl.java similarity index 68% rename from store/src/main/java/org/gephi/graph/impl/GraphModelImpl.java rename to src/main/java/org/gephi/graph/impl/GraphModelImpl.java index d1ad08d7..587359f0 100644 --- a/store/src/main/java/org/gephi/graph/impl/GraphModelImpl.java +++ b/src/main/java/org/gephi/graph/impl/GraphModelImpl.java @@ -15,48 +15,47 @@ */ package org.gephi.graph.impl; -import org.gephi.graph.api.AttributeUtils; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.function.Predicate; import org.gephi.graph.api.Configuration; -import org.gephi.graph.api.Index; -import org.gephi.graph.api.Table; -import org.gephi.graph.api.TimeFormat; -import org.gephi.graph.api.Interval; import org.gephi.graph.api.DirectedGraph; import org.gephi.graph.api.DirectedSubgraph; import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Element; import org.gephi.graph.api.Graph; import org.gephi.graph.api.GraphBridge; import org.gephi.graph.api.GraphFactory; import org.gephi.graph.api.GraphModel; import org.gephi.graph.api.GraphObserver; import org.gephi.graph.api.GraphView; +import org.gephi.graph.api.Index; +import org.gephi.graph.api.Interval; import org.gephi.graph.api.Node; -import org.gephi.graph.api.Origin; +import org.gephi.graph.api.SpatialIndex; import org.gephi.graph.api.Subgraph; +import org.gephi.graph.api.Table; +import org.gephi.graph.api.TimeFormat; +import org.gephi.graph.api.TimeIndex; import org.gephi.graph.api.UndirectedGraph; import org.gephi.graph.api.UndirectedSubgraph; -import org.gephi.graph.api.TimeIndex; -import org.joda.time.DateTimeZone; -import org.gephi.graph.api.TimeRepresentation; import org.gephi.graph.api.types.IntervalDoubleMap; -import org.gephi.graph.api.types.IntervalSet; import org.gephi.graph.api.types.TimestampDoubleMap; -import org.gephi.graph.api.types.TimestampSet; public class GraphModelImpl implements GraphModel { - protected final Configuration configuration; + protected final ConfigurationImpl configuration; protected final GraphStore store; protected final GraphBridgeImpl graphBridge; public GraphModelImpl() { - this(new Configuration()); + this(Configuration.builder().build()); } public GraphModelImpl(Configuration config) { checkValidConfiguration(config); - configuration = config.copy(); + configuration = new ConfigurationImpl(config); store = new GraphStore(this); graphBridge = new GraphBridgeImpl(store); } @@ -134,6 +133,11 @@ public void setVisibleView(GraphView view) { } } + @Override + public DefaultColumns defaultColumns() { + return store.defaultColumns; + } + @Override public int addEdgeType(Object label) { store.autoWriteLock(); @@ -184,6 +188,20 @@ public Object[] getEdgeTypeLabels() { } } + @Override + public Object[] getEdgeTypeLabels(boolean includeEmpty) { + if (includeEmpty) { + return getEdgeTypeLabels(); + } + store.autoReadLock(); + try { + return Arrays.stream(store.edgeTypeStore.getLabels()) + .filter(l -> store.getEdgeCount(store.edgeTypeStore.getId(l)) > 0).toArray(); + } finally { + store.autoReadUnlock(); + } + } + @Override public int[] getEdgeTypes() { store.autoReadLock(); @@ -234,6 +252,11 @@ public GraphView createView() { return store.viewStore.createView(); } + @Override + public GraphView createView(Predicate nodeFilter, Predicate edgeFilter) { + return store.viewStore.createView(nodeFilter, edgeFilter); + } + @Override public GraphView createView(boolean node, boolean edge) { return store.viewStore.createView(node, edge); @@ -286,6 +309,21 @@ public Index getNodeIndex(GraphView view) { return null; } + @Override + public Index getElementIndex(Table table) { + return getElementIndex(table, store.mainGraphView); + } + + @Override + public Index getElementIndex(Table table, GraphView view) { + if (table.isNodeTable()) { + return getNodeIndex(view); + } else if (table.isEdgeTable()) { + return getEdgeIndex(view); + } + return null; + } + @Override public Index getEdgeIndex() { return getEdgeIndex(store.mainGraphView); @@ -362,12 +400,12 @@ public void setTimeFormat(TimeFormat timeFormat) { } @Override - public DateTimeZone getTimeZone() { + public ZoneId getTimeZone() { return store.timeZone; } @Override - public void setTimeZone(DateTimeZone timeZone) { + public void setTimeZone(ZoneId timeZone) { store.timeZone = timeZone; } @@ -396,102 +434,13 @@ public Interval getTimeBounds(GraphView view) { @Override public Configuration getConfiguration() { - return configuration.copy(); + return configuration.toConfiguration(); } @Override public void setConfiguration(Configuration config) { - checkValidConfiguration(config); - - store.autoWriteLock(); - try { - if (store.getNodeCount() > 0 || !store.attributes.isEmpty() || store.nodeTable.countColumns() != GraphStoreConfiguration.NODE_DEFAULT_COLUMNS || store.edgeTable - .countColumns() != GraphStoreConfiguration.EDGE_DEFAULT_COLUMNS || store.edgeTypeStore.size() > 1) { - throw new IllegalStateException("The store should be empty when modifying the configuration"); - } - - if (!config.getNodeIdType().equals(configuration.getNodeIdType())) { - TableImpl nodeTable = store.nodeTable; - nodeTable.store.removeColumn(GraphStoreConfiguration.ELEMENT_ID_COLUMN_ID); - nodeTable.store.addColumn(new ColumnImpl(nodeTable, GraphStoreConfiguration.ELEMENT_ID_COLUMN_ID, - config.getNodeIdType(), "Id", null, Origin.PROPERTY, false, true)); - configuration.setNodeIdType(config.getNodeIdType()); - } - - if (!config.getEdgeIdType().equals(configuration.getEdgeIdType())) { - TableImpl edgeTable = store.edgeTable; - edgeTable.store.removeColumn(GraphStoreConfiguration.ELEMENT_ID_COLUMN_ID); - edgeTable.store.addColumn(new ColumnImpl(edgeTable, GraphStoreConfiguration.ELEMENT_ID_COLUMN_ID, - config.getEdgeIdType(), "Id", null, Origin.PROPERTY, false, true)); - configuration.setEdgeIdType(config.getEdgeIdType()); - } - - if (!config.getEdgeLabelType().equals(configuration.getEdgeLabelType())) { - configuration.setEdgeLabelType(config.getEdgeLabelType()); - } - - // Replace dynamic timeset columns if time representation changes: - if (!config.getTimeRepresentation().equals(configuration.getTimeRepresentation())) { - TableImpl nodeTable = store.nodeTable; - nodeTable.removeColumn(GraphStoreConfiguration.ELEMENT_TIMESET_COLUMN_ID); - TableImpl edgeTable = store.edgeTable; - edgeTable.removeColumn(GraphStoreConfiguration.ELEMENT_TIMESET_COLUMN_ID); - - if (config.getTimeRepresentation().equals(TimeRepresentation.TIMESTAMP)) { - nodeTable.store.addColumn(new ColumnImpl(nodeTable, - GraphStoreConfiguration.ELEMENT_TIMESET_COLUMN_ID, TimestampSet.class, "Timestamp", null, - Origin.PROPERTY, false, false)); - edgeTable.store.addColumn(new ColumnImpl(nodeTable, - GraphStoreConfiguration.ELEMENT_TIMESET_COLUMN_ID, TimestampSet.class, "Timestamp", null, - Origin.PROPERTY, false, false)); - } else { - nodeTable.store.addColumn(new ColumnImpl(nodeTable, - GraphStoreConfiguration.ELEMENT_TIMESET_COLUMN_ID, IntervalSet.class, "Interval", null, - Origin.PROPERTY, false, false)); - edgeTable.store.addColumn(new ColumnImpl(nodeTable, - GraphStoreConfiguration.ELEMENT_TIMESET_COLUMN_ID, IntervalSet.class, "Interval", null, - Origin.PROPERTY, false, false)); - } - configuration.setTimeRepresentation(config.getTimeRepresentation()); - store.timeStore.resetConfiguration(); - } - - // Change whether edge weight column - final boolean edgeWeightIndexed = AttributeUtils.isSimpleType(config.getEdgeWeightType()); - - if (!config.getEdgeWeightColumn().equals(configuration.getEdgeWeightColumn())) { - TableImpl edgeTable = store.edgeTable; - if (config.getEdgeWeightColumn()) { - edgeTable.store.garbageQueue.add(edgeTable.store - .intToShort(GraphStoreConfiguration.EDGE_WEIGHT_INDEX)); - edgeTable.store.addColumn(new ColumnImpl(edgeTable, GraphStoreConfiguration.EDGE_WEIGHT_COLUMN_ID, - config.getEdgeWeightType(), "Weight", null, Origin.PROPERTY, edgeWeightIndexed, false)); - } else { - edgeTable.removeColumn(GraphStoreConfiguration.EDGE_WEIGHT_COLUMN_ID); - edgeTable.store.garbageQueue.remove(edgeTable.store - .intToShort(GraphStoreConfiguration.EDGE_WEIGHT_INDEX)); - } - } - - // Change weight column type: - if (!config.getEdgeWeightType().equals(configuration.getEdgeWeightType())) { - TableImpl edgeTable = store.edgeTable; - - Class newWeightType = config.getEdgeWeightType(); - if (config.getEdgeWeightColumn()) { - edgeTable.removeColumn(GraphStoreConfiguration.EDGE_WEIGHT_COLUMN_ID); - - edgeTable.store.addColumn(new ColumnImpl(edgeTable, GraphStoreConfiguration.EDGE_WEIGHT_COLUMN_ID, - newWeightType, "Weight", null, Origin.PROPERTY, edgeWeightIndexed, false)); - } - - configuration.setEdgeWeightType(newWeightType); - } - - store.factory.resetConfiguration(); - } finally { - store.autoWriteUnlock(); - } + throw new UnsupportedOperationException( + "No longer supported. Configuration is immutable and needs to be passed at GraphModel creation time"); } @Override @@ -550,6 +499,9 @@ private void checkGraphObserver(GraphObserver observer) { } private void checkValidConfiguration(Configuration config) { + if (config == null) { + throw new NullPointerException("Configuration cannot be null, use Configuration.builder().build() instead"); + } Class edgeWeightType = config.getEdgeWeightType(); if (edgeWeightType.equals(Double.class)) { return;// Double is always allowed diff --git a/store/src/main/java/org/gephi/graph/impl/GraphObserverImpl.java b/src/main/java/org/gephi/graph/impl/GraphObserverImpl.java similarity index 87% rename from store/src/main/java/org/gephi/graph/impl/GraphObserverImpl.java rename to src/main/java/org/gephi/graph/impl/GraphObserverImpl.java index 9737c4cb..f812d122 100644 --- a/store/src/main/java/org/gephi/graph/impl/GraphObserverImpl.java +++ b/src/main/java/org/gephi/graph/impl/GraphObserverImpl.java @@ -17,7 +17,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectList; -import java.util.Collections; +import it.unimi.dsi.fastutil.objects.ObjectLists; import org.gephi.graph.api.Edge; import org.gephi.graph.api.EdgeIterable; import org.gephi.graph.api.Graph; @@ -119,10 +119,11 @@ protected void refreshDiff() { if (nodeVersion < graphVersion.nodeVersion) { int maxStoreId = graphStore.nodeStore.maxStoreId(); - for (Node n : nodeCache) { - NodeImpl nImpl = (NodeImpl) n; + for (int i = 0; i < nodeCache.length; i++) { + NodeImpl nImpl = nodeCache[i]; if (nImpl != null && !graph.contains(nImpl)) { graphDiff.removedNodes.add(nImpl); + nodeCache[i] = null; } } if (maxStoreId > nodeCache.length || maxStoreId < nodeCache.length) { @@ -145,10 +146,11 @@ protected void refreshDiff() { if (edgeVersion < graphVersion.edgeVersion) { int maxStoreId = graphStore.edgeStore.maxStoreId(); - for (Edge e : edgeCache) { - EdgeImpl eImpl = (EdgeImpl) e; + for (int i = 0; i < edgeCache.length; i++) { + EdgeImpl eImpl = edgeCache[i]; if (eImpl != null && !graph.contains(eImpl)) { graphDiff.removedEdges.add(eImpl); + edgeCache[i] = null; } } if (maxStoreId > edgeCache.length || maxStoreId < edgeCache.length) { @@ -196,7 +198,8 @@ public GraphDiffImpl() { @Override public NodeIterable getAddedNodes() { if (!addedNodes.isEmpty()) { - return graphStore.getNodeIterableWrapper(Collections.unmodifiableList(addedNodes).iterator(), false); + return new NodeIterableWrapper(() -> ObjectLists.unmodifiable(addedNodes).iterator(), + () -> ObjectLists.unmodifiable(addedNodes).spliterator(), null); } return NodeIterable.EMPTY; } @@ -204,7 +207,8 @@ public NodeIterable getAddedNodes() { @Override public NodeIterable getRemovedNodes() { if (!removedNodes.isEmpty()) { - return graphStore.getNodeIterableWrapper(Collections.unmodifiableList(removedNodes).iterator(), false); + return new NodeIterableWrapper(() -> ObjectLists.unmodifiable(removedNodes).iterator(), + () -> ObjectLists.unmodifiable(removedNodes).spliterator(), null); } return NodeIterable.EMPTY; } @@ -212,7 +216,8 @@ public NodeIterable getRemovedNodes() { @Override public EdgeIterable getAddedEdges() { if (!addedEdges.isEmpty()) { - return graphStore.getEdgeIterableWrapper(Collections.unmodifiableList(addedEdges).iterator(), false); + return new EdgeIterableWrapper(() -> ObjectLists.unmodifiable(addedEdges).iterator(), + () -> ObjectLists.unmodifiable(addedEdges).spliterator(), null); } return EdgeIterable.EMPTY; } @@ -220,7 +225,8 @@ public EdgeIterable getAddedEdges() { @Override public EdgeIterable getRemovedEdges() { if (!removedEdges.isEmpty()) { - return graphStore.getEdgeIterableWrapper(Collections.unmodifiableList(removedEdges).iterator(), false); + return new EdgeIterableWrapper(() -> ObjectLists.unmodifiable(removedEdges).iterator(), + () -> ObjectLists.unmodifiable(removedEdges).spliterator(), null); } return EdgeIterable.EMPTY; } diff --git a/store/src/main/java/org/gephi/graph/impl/GraphStore.java b/src/main/java/org/gephi/graph/impl/GraphStore.java similarity index 74% rename from store/src/main/java/org/gephi/graph/impl/GraphStore.java rename to src/main/java/org/gephi/graph/impl/GraphStore.java index 41ac5f5d..885e694a 100644 --- a/store/src/main/java/org/gephi/graph/impl/GraphStore.java +++ b/src/main/java/org/gephi/graph/impl/GraphStore.java @@ -13,38 +13,40 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.gephi.graph.impl; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; -import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.Set; -import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Configuration; -import org.gephi.graph.api.Origin; -import org.gephi.graph.api.TimeFormat; -import org.gephi.graph.api.Interval; -import org.gephi.graph.api.types.TimestampSet; import org.gephi.graph.api.DirectedGraph; import org.gephi.graph.api.DirectedSubgraph; import org.gephi.graph.api.Edge; import org.gephi.graph.api.EdgeIterable; +import org.gephi.graph.api.ElementIterable; import org.gephi.graph.api.Graph; import org.gephi.graph.api.GraphModel; import org.gephi.graph.api.GraphView; +import org.gephi.graph.api.Interval; import org.gephi.graph.api.Node; import org.gephi.graph.api.NodeIterable; -import org.gephi.graph.api.SpatialContext; +import org.gephi.graph.api.Origin; +import org.gephi.graph.api.SpatialIndex; import org.gephi.graph.api.Subgraph; -import org.joda.time.DateTimeZone; +import org.gephi.graph.api.Table; +import org.gephi.graph.api.TimeFormat; import org.gephi.graph.api.TimeRepresentation; import org.gephi.graph.api.types.IntervalSet; +import org.gephi.graph.api.types.TimestampSet; public class GraphStore implements DirectedGraph, DirectedSubgraph { protected final GraphModelImpl graphModel; - protected final Configuration configuration; + protected final ConfigurationImpl configuration; // Stores protected final NodeStore nodeStore; protected final EdgeStore edgeStore; @@ -57,7 +59,7 @@ public class GraphStore implements DirectedGraph, DirectedSubgraph { // Factory protected final GraphFactoryImpl factory; // Lock - protected final GraphLock lock; + protected final GraphLockImpl lock; // Version protected final GraphVersion version; protected final List observers; @@ -68,37 +70,43 @@ public class GraphStore implements DirectedGraph, DirectedSubgraph { // TimeFormat protected TimeFormat timeFormat; // Time zone - protected DateTimeZone timeZone; + protected ZoneId timeZone; // Spatial context - protected GraphStoreSpatialContextImpl spatialIndex; + protected SpatialIndexImpl spatialIndex; + // Default columns + protected final DefaultColumnsImpl defaultColumns; public GraphStore() { - this(null); + this(null, new ConfigurationImpl()); } public GraphStore(GraphModelImpl model) { - configuration = model != null ? model.configuration : new Configuration(); + this(model, model.configuration); + } + + protected GraphStore(GraphModelImpl model, Configuration config) { + this(model, new ConfigurationImpl(config)); + } + + protected GraphStore(GraphModelImpl model, ConfigurationImpl config) { + configuration = config; graphModel = model; - lock = new GraphLock(); - if (GraphStoreConfiguration.ENABLE_SPATIAL_INDEX) { - spatialIndex = new GraphStoreSpatialContextImpl(this); - } else { - spatialIndex = null; - } + lock = new GraphLockImpl(); edgeTypeStore = new EdgeTypeStore(); mainGraphView = new MainGraphView(); viewStore = new GraphViewStore(this); - version = GraphStoreConfiguration.ENABLE_OBSERVERS ? new GraphVersion(this) : null; - observers = GraphStoreConfiguration.ENABLE_OBSERVERS ? new ArrayList<>() : null; - edgeStore = new EdgeStore(edgeTypeStore, spatialIndex, GraphStoreConfiguration.ENABLE_AUTO_LOCKING ? lock - : null, viewStore, GraphStoreConfiguration.ENABLE_OBSERVERS ? version : null); - nodeStore = new NodeStore(edgeStore, spatialIndex, GraphStoreConfiguration.ENABLE_AUTO_LOCKING ? lock : null, - viewStore, GraphStoreConfiguration.ENABLE_OBSERVERS ? version : null); - nodeTable = new TableImpl<>(this, Node.class, GraphStoreConfiguration.ENABLE_INDEX_NODES); - edgeTable = new TableImpl<>(this, Edge.class, GraphStoreConfiguration.ENABLE_INDEX_EDGES); - timeStore = new TimeStore(this, GraphStoreConfiguration.ENABLE_AUTO_LOCKING ? lock : null, - GraphStoreConfiguration.ENABLE_INDEX_TIMESTAMP); + version = configuration.isEnableObservers() ? new GraphVersion(this) : null; + observers = configuration.isEnableObservers() ? new ArrayList<>() : null; + spatialIndex = configuration.isEnableSpatialIndex() ? new SpatialIndexImpl(this) : null; + edgeStore = new EdgeStore(edgeTypeStore, spatialIndex, configuration, + configuration.isEnableAutoLocking() ? lock : null, viewStore, + configuration.isEnableObservers() ? version : null); + nodeStore = new NodeStore(edgeStore, spatialIndex, configuration.isEnableAutoLocking() ? lock : null, viewStore, + configuration.isEnableObservers() ? version : null); + nodeTable = new TableImpl<>(this, Node.class); + edgeTable = new TableImpl<>(this, Edge.class); + timeStore = new TimeStore(this, configuration.isEnableIndexTime()); attributes = new GraphAttributesImpl(); factory = new GraphFactoryImpl(this); timeFormat = GraphStoreConfiguration.DEFAULT_TIME_FORMAT; @@ -107,10 +115,10 @@ public GraphStore(GraphModelImpl model) { undirectedDecorator = new UndirectedDecorator(this); // Default cols - nodeTable.store.addColumn(new ColumnImpl(nodeTable, GraphStoreConfiguration.ELEMENT_ID_COLUMN_ID, configuration - .getNodeIdType(), "Id", null, Origin.PROPERTY, false, true)); - edgeTable.store.addColumn(new ColumnImpl(edgeTable, GraphStoreConfiguration.ELEMENT_ID_COLUMN_ID, configuration - .getEdgeIdType(), "Id", null, Origin.PROPERTY, false, true)); + nodeTable.store.addColumn(new ColumnImpl(nodeTable, GraphStoreConfiguration.ELEMENT_ID_COLUMN_ID, + configuration.getNodeIdType(), "Id", null, Origin.PROPERTY, false, true)); + edgeTable.store.addColumn(new ColumnImpl(edgeTable, GraphStoreConfiguration.ELEMENT_ID_COLUMN_ID, + configuration.getEdgeIdType(), "Id", null, Origin.PROPERTY, false, true)); if (GraphStoreConfiguration.ENABLE_ELEMENT_LABEL) { nodeTable.store.addColumn(new ColumnImpl(nodeTable, GraphStoreConfiguration.ELEMENT_LABEL_COLUMN_ID, String.class, "Label", null, Origin.PROPERTY, false, false)); @@ -130,13 +138,13 @@ public GraphStore(GraphModelImpl model) { IntervalSet.class, "Interval", null, Origin.PROPERTY, false, false)); } } - if (configuration.getEdgeWeightColumn()) { - final boolean edgeWeightIndexed = AttributeUtils.isSimpleType(configuration.getEdgeWeightType()); + if (configuration.isEdgeWeightColumn()) { edgeTable.store.addColumn(new ColumnImpl(edgeTable, GraphStoreConfiguration.EDGE_WEIGHT_COLUMN_ID, - configuration.getEdgeWeightType(), "Weight", null, Origin.PROPERTY, edgeWeightIndexed, false)); + configuration.getEdgeWeightType(), "Weight", null, Origin.PROPERTY, false, false)); } else { edgeTable.store.length++; } + defaultColumns = new DefaultColumnsImpl(this); } @Override @@ -163,14 +171,6 @@ public boolean addAllNodes(final Collection nodes) { public boolean addEdge(final Edge edge) { autoWriteLock(); try { - int type = edge.getType(); - if (edgeTypeStore != null && !edgeTypeStore.contains(type)) { - if (GraphStoreConfiguration.ENABLE_AUTO_TYPE_REGISTRATION) { - edgeTypeStore.addType(String.valueOf(type), type); - } else { - throw new RuntimeException("The type doesn't exist"); - } - } return edgeStore.add(edge); } finally { autoWriteUnlock(); @@ -197,6 +197,16 @@ public NodeImpl getNode(final Object id) { } } + @Override + public NodeImpl getNodeByStoreId(final int id) { + autoReadLock(); + try { + return nodeStore.getForGetByStoreId(id); + } finally { + autoReadUnlock(); + } + } + @Override public boolean hasNode(final Object id) { return getNode(id) != null; @@ -212,6 +222,16 @@ public EdgeImpl getEdge(final Object id) { } } + @Override + public EdgeImpl getEdgeByStoreId(final int id) { + autoReadLock(); + try { + return edgeStore.getForGetByStoreId(id); + } finally { + autoReadUnlock(); + } + } + @Override public boolean hasEdge(final Object id) { return getEdge(id) != null; @@ -237,9 +257,19 @@ public EdgeIterable getEdges() { return edgeStore; } + protected ElementIterable getElements(Table table) { + return table.isNodeTable() ? nodeStore : edgeStore; + } + + @Override + public EdgeIterable getEdges(int type) { + return new EdgeIterableWrapper(() -> edgeStore.iteratorType(type, false), + () -> edgeStore.spliteratorType(type, false), getAutoLock()); + } + @Override public EdgeIterable getSelfLoops() { - return new EdgeIterableWrapper(edgeStore.iteratorSelfLoop()); + return new EdgeIterableWrapper(edgeStore::iteratorSelfLoop, edgeStore::spliteratorSelfLoop, getAutoLock()); } @Override @@ -247,7 +277,10 @@ public boolean removeNode(final Node node) { autoWriteLock(); try { nodeStore.checkNonNullNodeObject(node); - for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator((NodeImpl) node); edgeIterator + if (((NodeImpl) node).storeId == NodeStore.NULL_ID) { + return false; + } + for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(node, false); edgeIterator .hasNext();) { edgeIterator.next(); edgeIterator.remove(); @@ -274,7 +307,10 @@ public boolean removeAllNodes(Collection nodes) { try { for (Node node : nodes) { nodeStore.checkNonNullNodeObject(node); - for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator((NodeImpl) node); edgeIterator + if (((NodeImpl) node).storeId == NodeStore.NULL_ID) { + continue; + } + for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(node, false); edgeIterator .hasNext();) { edgeIterator.next(); edgeIterator.remove(); @@ -286,6 +322,16 @@ public boolean removeAllNodes(Collection nodes) { } } + @Override + public boolean retainNodes(Collection nodes) { + autoWriteLock(); + try { + return nodeStore.retainAll(nodes); + } finally { + autoWriteUnlock(); + } + } + @Override public boolean removeAllEdges(Collection edges) { autoWriteLock(); @@ -296,6 +342,16 @@ public boolean removeAllEdges(Collection edges) { } } + @Override + public boolean retainEdges(Collection edges) { + autoWriteLock(); + try { + return edgeStore.retainAll(edges); + } finally { + autoWriteUnlock(); + } + } + public NodeStore getNodeStore() { return nodeStore; } @@ -336,11 +392,7 @@ public Edge getEdge(final Node node1, final Node node2, final int type) { @Override public EdgeIterable getEdges(Node node1, Node node2, int type) { - Iterator itr = edgeStore.getAll(node1, node2, type, false); - if (itr != null) { - return new EdgeIterableWrapper(itr); - } - return EdgeIterable.EMPTY; + return new EdgeIterableWrapper(() -> edgeStore.getAll(node1, node2, type, false), getAutoLock()); } @Override @@ -355,71 +407,67 @@ public Edge getEdge(final Node node1, final Node node2) { @Override public EdgeIterable getEdges(Node node1, Node node2) { - Iterator itr = edgeStore.getAll(node1, node2, false); - if (itr != null) { - return new EdgeIterableWrapper(itr); - } - return EdgeIterable.EMPTY; + return new EdgeIterableWrapper(() -> edgeStore.getAll(node1, node2, false), getAutoLock()); } @Override public NodeIterable getNeighbors(final Node node) { - return new NodeIterableWrapper(edgeStore.neighborIterator(node)); + return new NodeIterableWrapper(() -> edgeStore.neighborIterator(node), getAutoLock()); } @Override public NodeIterable getNeighbors(final Node node, final int type) { - return new NodeIterableWrapper(edgeStore.neighborIterator(node, type)); + return new NodeIterableWrapper(() -> edgeStore.neighborIterator(node, type), getAutoLock()); } @Override public NodeIterable getPredecessors(final Node node) { - return new NodeIterableWrapper(edgeStore.neighborInIterator(node)); + return new NodeIterableWrapper(() -> edgeStore.neighborInIterator(node), getAutoLock()); } @Override public NodeIterable getPredecessors(final Node node, final int type) { - return new NodeIterableWrapper(edgeStore.neighborInIterator(node, type)); + return new NodeIterableWrapper(() -> edgeStore.neighborInIterator(node, type), getAutoLock()); } @Override public NodeIterable getSuccessors(final Node node) { - return new NodeIterableWrapper(edgeStore.neighborOutIterator(node)); + return new NodeIterableWrapper(() -> edgeStore.neighborOutIterator(node), getAutoLock()); } @Override public NodeIterable getSuccessors(final Node node, final int type) { - return new NodeIterableWrapper(edgeStore.neighborOutIterator(node, type)); + return new NodeIterableWrapper(() -> edgeStore.neighborOutIterator(node, type), getAutoLock()); } @Override public EdgeIterable getEdges(final Node node) { - return new EdgeIterableWrapper(edgeStore.edgeIterator(node)); + return new EdgeIterableWrapper(() -> edgeStore.edgeIterator(node, true), getAutoLock()); } @Override public EdgeIterable getEdges(final Node node, final int type) { - return new EdgeIterableWrapper(edgeStore.edgeIterator(node, type)); + return new EdgeIterableWrapper(() -> edgeStore.edgeIterator(node, type), getAutoLock()); } @Override public EdgeIterable getInEdges(final Node node) { - return new EdgeIterableWrapper(edgeStore.edgeInIterator(node)); + return new EdgeIterableWrapper(() -> edgeStore.edgeInIterator(node), getAutoLock()); } @Override public EdgeIterable getInEdges(final Node node, final int type) { - return new EdgeIterableWrapper(edgeStore.edgeInIterator(node, type)); + return new EdgeIterableWrapper(() -> edgeStore.edgeInIterator(node, type), getAutoLock()); } @Override public EdgeIterable getOutEdges(final Node node) { - return new EdgeIterableWrapper(edgeStore.edgeOutIterator(node)); + return new EdgeIterableWrapper(() -> edgeStore.edgeOutIterator(node), getAutoLock()); } @Override public EdgeIterable getOutEdges(final Node node, final int type) { - return new EdgeIterableWrapper(edgeStore.edgeOutIterator(node, type)); + return new EdgeIterableWrapper(() -> edgeStore.edgeOutIterator(node, type), getAutoLock()); } @Override @@ -535,7 +583,7 @@ public boolean isIncident(final Node node, final Edge edge) { public void clearEdges(final Node node) { autoWriteLock(); try { - EdgeStore.EdgeInOutIterator itr = edgeStore.edgeIterator(node); + EdgeStore.EdgeInOutIterator itr = edgeStore.edgeIterator(node, false); for (; itr.hasNext();) { itr.next(); itr.remove(); @@ -563,6 +611,11 @@ public void clearEdges(final Node node, final int type) { public void clear() { autoWriteLock(); try { + for (GraphViewImpl view : viewStore.views) { + if (view != null) { + view.clear(); + } + } edgeStore.clear(); nodeStore.clear(); edgeTypeStore.clear(); @@ -578,6 +631,11 @@ public void clear() { public void clearEdges() { autoWriteLock(); try { + for (GraphViewImpl view : viewStore.views) { + if (view != null) { + view.clearEdges(); + } + } edgeStore.clear(); edgeTypeStore.clear(); edgeTable.store.indexStore.clear(); @@ -667,32 +725,45 @@ public void writeUnlock() { lock.writeUnlock(); } + @Override + public GraphLockImpl getLock() { + return lock; + } + + @Override + public SpatialIndex getSpatialIndex() { + if (spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + return spatialIndex; + } + protected void autoReadLock() { - if (GraphStoreConfiguration.ENABLE_AUTO_LOCKING) { + if (configuration.isEnableAutoLocking()) { readLock(); } } protected void autoReadUnlock() { - if (GraphStoreConfiguration.ENABLE_AUTO_LOCKING) { + if (configuration.isEnableAutoLocking()) { readUnlock(); } } protected void autoReadUnlockAll() { - if (GraphStoreConfiguration.ENABLE_AUTO_LOCKING) { + if (configuration.isEnableAutoLocking()) { readUnlockAll(); } } protected void autoWriteLock() { - if (GraphStoreConfiguration.ENABLE_AUTO_LOCKING) { + if (configuration.isEnableAutoLocking()) { writeLock(); } } protected void autoWriteUnlock() { - if (GraphStoreConfiguration.ENABLE_AUTO_LOCKING) { + if (configuration.isEnableAutoLocking()) { writeUnlock(); } } @@ -757,6 +828,11 @@ public Graph getRootGraph() { return this; } + @Override + public int getVersion() { + return Objects.hash(version.nodeVersion, version.edgeVersion); + } + protected GraphObserverImpl createGraphObserver(Graph graph, boolean withDiff) { if (graph.getView() != mainGraphView) { throw new RuntimeException("This graph doesn't belong to this store"); @@ -781,20 +857,8 @@ protected void destroyGraphObserver(GraphObserverImpl observer) { } } - protected EdgeIterableWrapper getEdgeIterableWrapper(Iterator edgeIterator) { - return new EdgeIterableWrapper(edgeIterator); - } - - protected NodeIterableWrapper getNodeIterableWrapper(Iterator nodeIterator) { - return new NodeIterableWrapper(nodeIterator); - } - - protected EdgeIterableWrapper getEdgeIterableWrapper(Iterator edgeIterator, boolean blocking) { - return new EdgeIterableWrapper(edgeIterator, blocking); - } - - protected NodeIterableWrapper getNodeIterableWrapper(Iterator nodeIterator, boolean blocking) { - return new NodeIterableWrapper(nodeIterator, blocking); + protected GraphLockImpl getAutoLock() { + return configuration.isEnableAutoLocking() ? lock : null; } public int deepHashCode() { @@ -830,101 +894,6 @@ public boolean deepEquals(GraphStore obj) { return true; } - @Override - public SpatialContext getSpatialContext() { - return spatialIndex; - } - - protected class NodeIterableWrapper implements NodeIterable { - - protected final Iterator iterator; - protected final boolean blocking; - - public NodeIterableWrapper(Iterator iterator) { - this(iterator, true); - } - - public NodeIterableWrapper(Iterator iterator, boolean blocking) { - this.iterator = iterator; - this.blocking = blocking; - } - - @Override - public Iterator iterator() { - return iterator; - } - - @Override - public Node[] toArray() { - List list = new ArrayList<>(); - for (; iterator.hasNext();) { - list.add(iterator.next()); - } - return list.toArray(new Node[0]); - } - - @Override - public Collection toCollection() { - List list = new ArrayList<>(); - for (; iterator.hasNext();) { - list.add(iterator.next()); - } - return list; - } - - @Override - public void doBreak() { - if (blocking) { - autoReadUnlock(); - } - } - } - - protected class EdgeIterableWrapper implements EdgeIterable { - - protected final Iterator iterator; - protected final boolean blocking; - - public EdgeIterableWrapper(Iterator iterator) { - this(iterator, true); - } - - public EdgeIterableWrapper(Iterator iterator, boolean blocking) { - this.iterator = iterator; - this.blocking = blocking; - } - - @Override - public Iterator iterator() { - return iterator; - } - - @Override - public Edge[] toArray() { - List list = new ArrayList<>(); - for (; iterator.hasNext();) { - list.add(iterator.next()); - } - return list.toArray(new Edge[0]); - } - - @Override - public Collection toCollection() { - List list = new ArrayList<>(); - for (; iterator.hasNext();) { - list.add(iterator.next()); - } - return list; - } - - @Override - public void doBreak() { - if (blocking) { - autoReadUnlock(); - } - } - } - private final class MainGraphView implements GraphView { @Override diff --git a/store/src/main/java/org/gephi/graph/impl/GraphStoreConfiguration.java b/src/main/java/org/gephi/graph/impl/GraphStoreConfiguration.java similarity index 65% rename from store/src/main/java/org/gephi/graph/impl/GraphStoreConfiguration.java rename to src/main/java/org/gephi/graph/impl/GraphStoreConfiguration.java index 438d6d0e..71a640d1 100644 --- a/store/src/main/java/org/gephi/graph/impl/GraphStoreConfiguration.java +++ b/src/main/java/org/gephi/graph/impl/GraphStoreConfiguration.java @@ -15,34 +15,35 @@ */ package org.gephi.graph.impl; +import java.time.ZoneId; import org.gephi.graph.api.Estimator; import org.gephi.graph.api.TimeFormat; import org.gephi.graph.api.TimeRepresentation; -import org.joda.time.DateTimeZone; public final class GraphStoreConfiguration { // Features - public static final boolean ENABLE_AUTO_LOCKING = true; - public static final boolean ENABLE_AUTO_TYPE_REGISTRATION = true; - public static final boolean ENABLE_INDEX_NODES = true; - public static final boolean ENABLE_INDEX_EDGES = true; - public static final boolean ENABLE_INDEX_TIMESTAMP = true; - public static final boolean ENABLE_OBSERVERS = true; - public static final boolean ENABLE_NODE_PROPERTIES = true; - public static final boolean ENABLE_EDGE_PROPERTIES = true; - public static final boolean ENABLE_PARALLEL_EDGES = true; - public static final boolean ENABLE_SPATIAL_INDEX = true; + public static final boolean DEFAULT_ENABLE_AUTO_LOCKING = true; + public static final boolean DEFAULT_ENABLE_AUTO_EDGE_TYPE_REGISTRATION = true; + public static final boolean DEFAULT_ENABLE_INDEX_NODES = true; + public static final boolean DEFAULT_ENABLE_INDEX_EDGES = true; + public static final boolean DEFAULT_ENABLE_INDEX_TIME = true; + public static final boolean DEFAULT_ENABLE_OBSERVERS = true; + public static final boolean DEFAULT_ENABLE_NODE_PROPERTIES = true; + public static final boolean DEFAULT_ENABLE_EDGE_PROPERTIES = true; + public static final boolean DEFAULT_ENABLE_SPATIAL_INDEX = true; + public static final boolean DEFAULT_ENABLE_EDGE_WEIGHT_COLUMN = true; + public static final boolean DEFAULT_ENABLE_PARALLEL_EDGES_SAME_TYPE = true; // NodeStore - public final static int NODESTORE_BLOCK_SIZE = 5000; - public final static int NODESTORE_DEFAULT_BLOCKS = 10; - public static final int NODESTORE_DEFAULT_DICTIONARY_SIZE = 1000; + public final static int NODESTORE_BLOCK_SIZE = 8192; + public final static int NODESTORE_DEFAULT_BLOCKS = 5; + public static final int NODESTORE_DEFAULT_DICTIONARY_SIZE = 8192; public final static float NODESTORE_DICTIONARY_LOAD_FACTOR = .7f; // EdgeStore - public static final int EDGESTORE_BLOCK_SIZE = 8192; - public static final int EDGESTORE_DEFAULT_BLOCKS = 10; + public static final int EDGESTORE_BLOCK_SIZE = 32768; + public static final int EDGESTORE_DEFAULT_BLOCKS = 5; public static final int EDGESTORE_DEFAULT_TYPE_COUNT = 1; - public static final int EDGESTORE_DEFAULT_DICTIONARY_SIZE = 1000; + public static final int EDGESTORE_DEFAULT_DICTIONARY_SIZE = 32768; public static final float EDGESTORE_DICTIONARY_LOAD_FACTOR = .7f; // GraphView public static final int VIEW_DEFAULT_TYPE_COUNT = 1; @@ -63,6 +64,10 @@ public final class GraphStoreConfiguration { public static final String ELEMENT_LABEL_COLUMN_ID = "label"; public static final String ELEMENT_TIMESET_COLUMN_ID = "timeset"; public static final String EDGE_WEIGHT_COLUMN_ID = "weight"; + public static final String NODE_DEGREE_COLUMN_ID = "degree"; + public static final String NODE_IN_DEGREE_COLUMN_ID = "indegree"; + public static final String NODE_OUT_DEGREE_COLUMN_ID = "outdegree"; + public static final String EDGE_TYPE_COLUMN_ID = "type"; // Properties index public static final int ELEMENT_ID_INDEX = 0; public static final int ELEMENT_LABEL_INDEX = 1; @@ -72,17 +77,29 @@ public final class GraphStoreConfiguration { // TimeFormat public static final TimeFormat DEFAULT_TIME_FORMAT = TimeFormat.DOUBLE; // Time zone - public static final DateTimeZone DEFAULT_TIME_ZONE = DateTimeZone.UTC; + public static final ZoneId DEFAULT_TIME_ZONE = ZoneId.of("UTC"); // Dynamics public static final Estimator DEFAULT_ESTIMATOR = Estimator.FIRST; public static final TimeRepresentation DEFAULT_TIME_REPRESENTATION = TimeRepresentation.TIMESTAMP; // Spatial index + public static final int SPATIAL_INDEX_MAX_LEVELS = 16; + public static final int SPATIAL_INDEX_MAX_OBJECTS_PER_NODE = 8192; public static final float SPATIAL_INDEX_DIMENSION_BOUNDARY = 1e6f; + public static final boolean SPATIAL_INDEX_APPROXIMATE_AREA_SEARCH = false; + public static final float SPATIAL_INDEX_LOCAL_ITERATOR_THRESHOLD = 0.3f; // Miscellaneous public static final double TIMESTAMP_STORE_GROWING_FACTOR = 1.1; public static final double INTERVAL_STORE_GROWING_FACTOR = 1.1; + + /** + * Default number of node property columns. + */ public static final int NODE_DEFAULT_COLUMNS = 1 + (ENABLE_ELEMENT_LABEL ? 1 : 0) + (ENABLE_ELEMENT_TIME_SET ? 1 : 0); + + /** + * Default number of edge property columns. + */ public static final int EDGE_DEFAULT_COLUMNS = 2 + (ENABLE_ELEMENT_LABEL ? 1 : 0) + (ENABLE_ELEMENT_TIME_SET ? 1 : 0); } diff --git a/store/src/main/java/org/gephi/graph/impl/GraphVersion.java b/src/main/java/org/gephi/graph/impl/GraphVersion.java similarity index 96% rename from store/src/main/java/org/gephi/graph/impl/GraphVersion.java rename to src/main/java/org/gephi/graph/impl/GraphVersion.java index d436325e..fcb717e4 100644 --- a/store/src/main/java/org/gephi/graph/impl/GraphVersion.java +++ b/src/main/java/org/gephi/graph/impl/GraphVersion.java @@ -45,6 +45,14 @@ public int incrementAndGetEdgeVersion() { return edgeVersion; } + public int getNodeVersion() { + return nodeVersion; + } + + public int getEdgeVersion() { + return edgeVersion; + } + private void handleNodeReset() { if (graph != null) { if (graph.getView().isMainView()) { diff --git a/store/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java b/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java similarity index 57% rename from store/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java rename to src/main/java/org/gephi/graph/impl/GraphViewDecorator.java index 3c7db5a3..1d4f83c8 100644 --- a/store/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java +++ b/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java @@ -18,7 +18,10 @@ import java.util.Collection; import java.util.Iterator; import java.util.Set; +import java.util.Spliterator; +import java.util.ConcurrentModificationException; import java.util.function.Consumer; +import java.util.function.Predicate; import org.gephi.graph.api.DirectedSubgraph; import org.gephi.graph.api.Edge; import org.gephi.graph.api.EdgeIterable; @@ -29,11 +32,11 @@ import org.gephi.graph.api.Node; import org.gephi.graph.api.NodeIterable; import org.gephi.graph.api.Rect2D; -import org.gephi.graph.api.SpatialContext; +import org.gephi.graph.api.SpatialIndex; import org.gephi.graph.api.Subgraph; import org.gephi.graph.api.UndirectedSubgraph; -public class GraphViewDecorator implements DirectedSubgraph, UndirectedSubgraph, SpatialContext { +public class GraphViewDecorator implements DirectedSubgraph, UndirectedSubgraph, SpatialIndex { protected final boolean undirected; protected final GraphViewImpl view; @@ -61,8 +64,9 @@ public Edge getEdge(Node node1, Node node2) { @Override public EdgeIterable getEdges(Node node1, Node node2) { - return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore - .getAll(node1, node2, undirected))); + return new EdgeIterableWrapper( + () -> new EdgeViewIterator(graphStore.edgeStore.getAll(node1, node2, undirected)), + graphStore.getAutoLock()); } @Override @@ -81,8 +85,9 @@ public Edge getEdge(Node node1, Node node2, int type) { @Override public EdgeIterable getEdges(Node node1, Node node2, int type) { - return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore - .getAll(node1, node2, type, undirected))); + return new EdgeIterableWrapper( + () -> new EdgeViewIterator(graphStore.edgeStore.getAll(node1, node2, type, undirected)), + graphStore.getAutoLock()); } @Override @@ -102,54 +107,61 @@ public Edge getMutualEdge(Edge e) { @Override public NodeIterable getPredecessors(Node node) { checkValidInViewNodeObject(node); - return graphStore.getNodeIterableWrapper(new NeighborsIterator((NodeImpl) node, new EdgeViewIterator( - graphStore.edgeStore.edgeInIterator(node)))); + return new NodeIterableWrapper(() -> new NeighborsIterator((NodeImpl) node, + new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node))), graphStore.getAutoLock()); } @Override public NodeIterable getPredecessors(Node node, int type) { checkValidInViewNodeObject(node); - return graphStore.getNodeIterableWrapper(new NeighborsIterator((NodeImpl) node, new EdgeViewIterator( - graphStore.edgeStore.edgeInIterator(node, type)))); + return new NodeIterableWrapper( + () -> new NeighborsIterator((NodeImpl) node, + new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node, type))), + graphStore.getAutoLock()); } @Override public NodeIterable getSuccessors(Node node) { checkValidInViewNodeObject(node); - return graphStore.getNodeIterableWrapper(new NeighborsIterator((NodeImpl) node, new EdgeViewIterator( - graphStore.edgeStore.edgeOutIterator(node)))); + return new NodeIterableWrapper(() -> new NeighborsIterator((NodeImpl) node, + new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node))), graphStore.getAutoLock()); } @Override public NodeIterable getSuccessors(Node node, int type) { checkValidInViewNodeObject(node); - return graphStore.getNodeIterableWrapper(new NeighborsIterator((NodeImpl) node, new EdgeViewIterator( - graphStore.edgeStore.edgeOutIterator(node, type)))); + return new NodeIterableWrapper( + () -> new NeighborsIterator((NodeImpl) node, + new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node, type))), + graphStore.getAutoLock()); } @Override public EdgeIterable getInEdges(Node node) { checkValidInViewNodeObject(node); - return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node))); + return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node)), + graphStore.getAutoLock()); } @Override public EdgeIterable getInEdges(Node node, int type) { checkValidInViewNodeObject(node); - return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node, type))); + return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node, type)), + graphStore.getAutoLock()); } @Override public EdgeIterable getOutEdges(Node node) { checkValidInViewNodeObject(node); - return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node))); + return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node)), + graphStore.getAutoLock()); } @Override public EdgeIterable getOutEdges(Node node, int type) { checkValidInViewNodeObject(node); - return graphStore - .getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node, type))); + return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node, type)), + graphStore.getAutoLock()); } @Override @@ -263,12 +275,32 @@ public boolean removeAllNodes(Collection nodes) { } } + @Override + public boolean retainNodes(Collection nodes) { + graphStore.autoWriteLock(); + try { + return view.retainNodes(nodes); + } finally { + graphStore.autoWriteUnlock(); + } + } + + @Override + public boolean retainEdges(Collection edges) { + graphStore.autoWriteLock(); + try { + return view.retainEdges(edges); + } finally { + graphStore.autoWriteUnlock(); + } + } + @Override public boolean contains(Node node) { checkValidNodeObject(node); graphStore.autoReadLock(); try { - return view.containsNode((NodeImpl) node); + return view.containsNode(node); } finally { graphStore.autoReadUnlock(); } @@ -299,6 +331,20 @@ public Node getNode(Object id) { } } + @Override + public Node getNodeByStoreId(int id) { + graphStore.autoReadLock(); + try { + NodeImpl node = graphStore.getNodeByStoreId(id); + if (node != null && view.containsNode(node)) { + return node; + } + return null; + } finally { + graphStore.autoReadUnlock(); + } + } + @Override public boolean hasNode(final Object id) { return getNode(id) != null; @@ -318,6 +364,20 @@ public Edge getEdge(Object id) { } } + @Override + public Edge getEdgeByStoreId(int id) { + graphStore.autoReadLock(); + try { + EdgeImpl edge = graphStore.getEdgeByStoreId(id); + if (edge != null && view.containsEdge(edge)) { + return edge; + } + return null; + } finally { + graphStore.autoReadUnlock(); + } + } + @Override public boolean hasEdge(final Object id) { return getEdge(id) != null; @@ -325,45 +385,81 @@ public boolean hasEdge(final Object id) { @Override public NodeIterable getNodes() { - return graphStore.getNodeIterableWrapper(new NodeViewIterator(graphStore.nodeStore.iterator())); + if (!view.isNodeView()) { + return graphStore.getNodes(); + } + return new NodeIterableWrapper(() -> new NodeViewIterator(graphStore.nodeStore.iterator()), + NodeViewSpliterator::new, graphStore.getAutoLock()); } @Override public EdgeIterable getEdges() { if (undirected) { - return graphStore.getEdgeIterableWrapper(new UndirectedEdgeViewIterator(graphStore.edgeStore.iterator())); + return new EdgeIterableWrapper(() -> new UndirectedEdgeViewIterator(graphStore.edgeStore.iterator()), + () -> graphStore.edgeStore + .newFilteredSizedSpliterator(e -> view.containsEdge(e) && !isUndirectedToIgnore(e), view + .getUndirectedEdgeCount()), + graphStore.getAutoLock()); } else { - return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.iterator())); + return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.iterator()), + () -> graphStore.edgeStore.newFilteredSizedSpliterator(view::containsEdge, view.getEdgeCount()), + graphStore.getAutoLock()); + } + } + + @Override + public EdgeIterable getEdges(int type) { + if (undirected) { + return new EdgeIterableWrapper( + () -> new UndirectedEdgeViewIterator(graphStore.edgeStore.iteratorType(type, undirected)), + () -> graphStore.edgeStore.newFilteredSizedSpliterator(e -> e.getType() == type && view + .containsEdge(e) && !isUndirectedToIgnore(e), view.getUndirectedEdgeCount(type)), + graphStore.getAutoLock()); + } else { + return new EdgeIterableWrapper( + () -> new EdgeViewIterator(graphStore.edgeStore.iteratorType(type, undirected)), + () -> graphStore.edgeStore + .newFilteredSizedSpliterator(e -> e.getType() == type && view.containsEdge(e), view + .getEdgeCount(type)), + graphStore.getAutoLock()); } } @Override public EdgeIterable getSelfLoops() { - return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.iteratorSelfLoop())); + return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.iteratorSelfLoop()), + () -> graphStore.edgeStore.newFilteredSpliterator(e -> e.isSelfLoop() && view.containsEdge(e)), + graphStore.getAutoLock()); } @Override public NodeIterable getNeighbors(Node node) { checkValidInViewNodeObject(node); - return graphStore.getNodeIterableWrapper(new NeighborsIterator((NodeImpl) node, new UndirectedEdgeViewIterator( - graphStore.edgeStore.edgeIterator(node)))); + return new NodeIterableWrapper( + () -> new NeighborsIterator((NodeImpl) node, + new UndirectedEdgeViewIterator(graphStore.edgeStore.edgeIterator(node, true))), + graphStore.getAutoLock()); } @Override public NodeIterable getNeighbors(Node node, int type) { checkValidInViewNodeObject(node); - return graphStore.getNodeIterableWrapper(new NeighborsIterator((NodeImpl) node, new UndirectedEdgeViewIterator( - graphStore.edgeStore.edgeIterator(node, type)))); + return new NodeIterableWrapper( + () -> new NeighborsIterator((NodeImpl) node, + new UndirectedEdgeViewIterator(graphStore.edgeStore.edgeIterator(node, type))), + graphStore.getAutoLock()); } @Override public EdgeIterable getEdges(Node node) { checkValidInViewNodeObject(node); if (undirected) { - return graphStore.getEdgeIterableWrapper(new UndirectedEdgeViewIterator(graphStore.edgeStore - .edgeIterator(node))); + return new EdgeIterableWrapper( + () -> new UndirectedEdgeViewIterator(graphStore.edgeStore.edgeIterator(node, true)), + graphStore.getAutoLock()); } else { - return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.edgeIterator(node))); + return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.edgeIterator(node, true)), + graphStore.getAutoLock()); } } @@ -371,13 +467,13 @@ public EdgeIterable getEdges(Node node) { public EdgeIterable getEdges(Node node, int type) { checkValidInViewNodeObject(node); if (undirected) { - return graphStore.getEdgeIterableWrapper(new UndirectedEdgeViewIterator(graphStore.edgeStore - .edgeIterator(node, type))); + return new EdgeIterableWrapper( + () -> new UndirectedEdgeViewIterator(graphStore.edgeStore.edgeIterator(node, type)), + graphStore.getAutoLock()); } else { - return graphStore - .getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.edgeIterator(node, type))); + return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.edgeIterator(node, type)), + graphStore.getAutoLock()); } - } @Override @@ -415,7 +511,7 @@ public Node getOpposite(Node node, Edge edge) { public int getDegree(Node node) { if (undirected) { int count = 0; - EdgeStore.EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node); + EdgeStore.EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node, true); while (itr.hasNext()) { EdgeImpl edge = itr.next(); if (view.containsEdge(edge) && !isUndirectedToIgnore(edge)) { @@ -428,7 +524,7 @@ public int getDegree(Node node) { return count; } else { int count = 0; - EdgeStore.EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node); + EdgeStore.EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node, true); while (itr.hasNext()) { EdgeImpl edge = itr.next(); if (view.containsEdge(edge)) { @@ -506,8 +602,8 @@ public boolean isIncident(final Node node, final Edge edge) { public void clearEdges(Node node) { graphStore.autoWriteLock(); try { - EdgeStore.EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node); - for (; itr.hasNext();) { + EdgeStore.EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node, false); + while (itr.hasNext()) { EdgeImpl edge = itr.next(); view.removeEdge(edge); } @@ -521,7 +617,7 @@ public void clearEdges(Node node, int type) { graphStore.autoWriteLock(); try { EdgeStore.EdgeTypeInOutIterator itr = graphStore.edgeStore.edgeIterator(node, type); - for (; itr.hasNext();) { + while (itr.hasNext()) { EdgeImpl edge = itr.next(); view.removeEdge(edge); } @@ -595,6 +691,11 @@ public GraphModel getModel() { return graphStore.graphModel; } + @Override + public int getVersion() { + return view.getVersion(); + } + @Override public boolean isDirected() { return graphStore.isDirected(); @@ -630,6 +731,11 @@ public void writeLock() { graphStore.lock.writeLock(); } + @Override + public GraphLockImpl getLock() { + return graphStore.lock; + } + @Override public void writeUnlock() { graphStore.lock.writeUnlock(); @@ -689,6 +795,14 @@ public Graph getRootGraph() { return graphStore; } + @Override + public SpatialIndex getSpatialIndex() { + if (graphStore.spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + return this; + } + void checkWriteLock() { if (graphStore.lock != null) { graphStore.lock.checkHoldWriteLock(); @@ -702,7 +816,7 @@ void checkValidNodeObject(final Node n) { if (!(n instanceof NodeImpl)) { throw new ClassCastException("Object must be a NodeImpl object"); } - if (((NodeImpl) n).storeId == NodeStore.NULL_ID) { + if (n.getStoreId() == NodeStore.NULL_ID) { throw new IllegalArgumentException("Node should belong to a store"); } } @@ -710,7 +824,7 @@ void checkValidNodeObject(final Node n) { void checkValidInViewNodeObject(final Node n) { checkValidNodeObject(n); - if (!view.containsNode((NodeImpl) n)) { + if (!view.containsNode(n)) { throw new RuntimeException("Node doesn't belong to this view"); } } @@ -722,7 +836,7 @@ void checkValidEdgeObject(final Edge n) { if (!(n instanceof EdgeImpl)) { throw new ClassCastException("Object must be a EdgeImpl object"); } - if (((EdgeImpl) n).storeId == EdgeStore.NULL_ID) { + if (n.getStoreId() == EdgeStore.NULL_ID) { throw new IllegalArgumentException("Edge should belong to a store"); } } @@ -730,7 +844,7 @@ void checkValidEdgeObject(final Edge n) { void checkValidInViewEdgeObject(final Edge e) { checkValidEdgeObject(e); - if (!view.containsEdge((EdgeImpl) e)) { + if (!view.containsEdge(e)) { throw new RuntimeException("Edge doesn't belong to this view"); } } @@ -757,44 +871,246 @@ boolean isUndirectedToIgnore(final EdgeImpl edge) { } @Override - public SpatialContext getSpatialContext() { - return this; + public NodeIterable getNodesInArea(Rect2D rect) { + if (graphStore.spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + return graphStore.spatialIndex.getNodesInArea(rect, view::containsNode); } @Override - public NodeIterable getNodesInArea(Rect2D rect) { - Iterator iterator = graphStore.spatialIndex.getNodesInArea(rect).iterator(); - return graphStore.spatialIndex.getNodeIterableWrapper(new NodeViewIterator(iterator)); + public NodeIterable getNodesInArea(Rect2D rect, Predicate predicate) { + if (graphStore.spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + return graphStore.spatialIndex.getNodesInArea(rect, (node) -> view.containsNode(node) && predicate.test(node)); } @Override - public void getNodesInArea(Rect2D rect, final Consumer callback) { - graphStore.spatialIndex.getNodesInArea(rect, new Consumer() { - @Override - public void accept(Node node) { - if (view.containsNode((NodeImpl) node)) { - callback.accept(node); - } - } - }); + public NodeIterable getApproximateNodesInArea(Rect2D rect) { + if (graphStore.spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + return graphStore.spatialIndex.getApproximateNodesInArea(rect, view::containsNode); + } + + @Override + public NodeIterable getApproximateNodesInArea(Rect2D rect, Predicate predicate) { + if (graphStore.spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + return graphStore.spatialIndex + .getApproximateNodesInArea(rect, (node) -> view.containsNode(node) && predicate.test(node)); } @Override public EdgeIterable getEdgesInArea(Rect2D rect) { - Iterator iterator = graphStore.spatialIndex.getEdgesInArea(rect).iterator(); - return graphStore.spatialIndex.getEdgeIterableWrapper(new EdgeViewIterator(iterator)); + if (graphStore.spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + return graphStore.spatialIndex.getEdgesInArea(rect, view::containsEdge); + } + + @Override + public EdgeIterable getEdgesInArea(Rect2D rect, Predicate predicate) { + if (graphStore.spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + return graphStore.spatialIndex.getEdgesInArea(rect, (edge) -> view.containsEdge(edge) && predicate.test(edge)); + } + + @Override + public EdgeIterable getApproximateEdgesInArea(Rect2D rect) { + if (graphStore.spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + return graphStore.spatialIndex.getApproximateEdgesInArea(rect, view::containsEdge); + } + + @Override + public EdgeIterable getApproximateEdgesInArea(Rect2D rect, Predicate predicate) { + if (graphStore.spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + return graphStore.spatialIndex + .getApproximateEdgesInArea(rect, (edge) -> view.containsEdge(edge) && predicate.test(edge)); } @Override - public void getEdgesInArea(Rect2D rect, final Consumer callback) { - graphStore.spatialIndex.getEdgesInArea(rect, new Consumer() { - @Override - public void accept(Edge edge) { - if (view.containsEdge((EdgeImpl) edge)) { - callback.accept(edge); + public Rect2D getBoundaries() { + if (graphStore.spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + return graphStore.spatialIndex.getBoundaries(view::containsNode); + } + + @Override + public void spatialIndexReadLock() { + if (graphStore.spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + graphStore.spatialIndex.spatialIndexReadLock(); + } + + @Override + public void spatialIndexReadUnlock() { + if (graphStore.spatialIndex == null) { + throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)"); + } + graphStore.spatialIndex.spatialIndexReadUnlock(); + } + + private final class NodeViewSpliterator implements Spliterator { + + private final int endBlockExclusive; + private int blockIndex; + private int indexInBlock; + private NodeImpl[] currentArray; + private int currentLength; + private final int expectedVersion; + private int totalSize; + private int consumed; + // True only for the root spliterator, where totalSize comes from view.getNodeCount(). + // Sub-ranges only have a proportional estimate and must drop SIZED. + private boolean exactSize; + + NodeViewSpliterator() { + this(0, graphStore.nodeStore.blocksCount); + } + + NodeViewSpliterator(int startBlock, int endBlockExclusive) { + this.blockIndex = startBlock; + this.endBlockExclusive = endBlockExclusive; + this.expectedVersion = graphStore.version != null ? graphStore.version.getNodeVersion() : 0; + this.consumed = 0; + + // Root spliterator uses the exact view count; sub-ranges fall back to an estimate. + if (startBlock == 0 && endBlockExclusive == graphStore.nodeStore.blocksCount) { + this.totalSize = view.getNodeCount(); + this.exactSize = true; + } else { + this.totalSize = computeSizeEstimate(startBlock, endBlockExclusive); + this.exactSize = false; + } + + if (startBlock < endBlockExclusive) { + NodeStore.NodeBlock b = graphStore.nodeStore.blocks[startBlock]; + currentArray = b.backingArray; + currentLength = b.nodeLength; + indexInBlock = 0; + } else { + currentArray = null; + currentLength = 0; + indexInBlock = 0; + } + } + + private void advanceBlock() { + blockIndex++; + if (blockIndex < endBlockExclusive) { + NodeStore.NodeBlock b = graphStore.nodeStore.blocks[blockIndex]; + currentArray = b.backingArray; + currentLength = b.nodeLength; + indexInBlock = 0; + } else { + currentArray = null; + currentLength = 0; + indexInBlock = 0; + } + } + + private void checkForComodification() { + if (graphStore.version != null && expectedVersion != graphStore.version.getNodeVersion()) { + throw new ConcurrentModificationException(); + } + } + + private int computeSizeEstimate(int start, int end) { + int sum = 0; + for (int i = start; i < end; i++) { + NodeStore.NodeBlock b = graphStore.nodeStore.blocks[i]; + if (b != null) { + // Exact count: nodeLength minus garbageLength + sum += (b.nodeLength - b.garbageLength); } } - }); + if (sum > 0) { + // Scale by view ratio to estimate number of nodes in view + double viewRatio = (double) view.getNodeCount() / graphStore.nodeStore.size; + sum = (int) Math.round(sum * viewRatio); + } + return sum; + } + + @Override + public boolean tryAdvance(Consumer action) { + checkForComodification(); + while (currentArray != null) { + while (indexInBlock < currentLength) { + NodeImpl n = currentArray[indexInBlock++]; + if (n != null && view.containsNode(n)) { + consumed++; + action.accept(n); + return true; + } + } + advanceBlock(); + } + return false; + } + + @Override + public Spliterator trySplit() { + // Only split at block boundaries to preserve encounter order + if (indexInBlock != 0) { + return null; + } + + int currentPos = blockIndex; + int remainingBlocks = endBlockExclusive - currentPos; + + if (remainingBlocks <= 1) { + return null; + } + + int mid = currentPos + remainingBlocks / 2; + + // Create left half + NodeViewSpliterator left = new NodeViewSpliterator(currentPos, mid); + + // Update this spliterator to become the right half + blockIndex = mid; + if (mid < endBlockExclusive) { + NodeStore.NodeBlock b = graphStore.nodeStore.blocks[mid]; + currentArray = b.backingArray; + currentLength = b.nodeLength; + indexInBlock = 0; + } else { + currentArray = null; + currentLength = 0; + indexInBlock = 0; + } + + this.totalSize = Math.max(0, totalSize - left.totalSize); + // Once split, neither half can guarantee an exact view-aware size, so drop SIZED. + this.exactSize = false; + left.exactSize = false; + + return left; + } + + @Override + public long estimateSize() { + // Use the exact view size minus what we've consumed + long remaining = totalSize - consumed; + return remaining < 0 ? 0 : remaining; + } + + @Override + public int characteristics() { + int base = Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL; + return exactSize ? base | Spliterator.SIZED : base; + } } protected final class NodeViewIterator implements Iterator { @@ -901,7 +1217,7 @@ public void remove() { } } - protected class NeighborsIterator implements Iterator { + protected static class NeighborsIterator implements Iterator { protected final NodeImpl node; protected final Iterator itr; diff --git a/store/src/main/java/org/gephi/graph/impl/GraphViewImpl.java b/src/main/java/org/gephi/graph/impl/GraphViewImpl.java similarity index 54% rename from store/src/main/java/org/gephi/graph/impl/GraphViewImpl.java rename to src/main/java/org/gephi/graph/impl/GraphViewImpl.java index 24dcc42a..b4b6f885 100644 --- a/store/src/main/java/org/gephi/graph/impl/GraphViewImpl.java +++ b/src/main/java/org/gephi/graph/impl/GraphViewImpl.java @@ -15,18 +15,19 @@ */ package org.gephi.graph.impl; -import cern.colt.bitvector.BitVector; -import cern.colt.bitvector.QuickBitVector; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.Collection; import java.util.Iterator; import java.util.List; -import org.gephi.graph.api.Interval; +import java.util.Objects; +import java.util.function.Predicate; import org.gephi.graph.api.DirectedSubgraph; import org.gephi.graph.api.Edge; import org.gephi.graph.api.Graph; import org.gephi.graph.api.GraphView; +import org.gephi.graph.api.Interval; import org.gephi.graph.api.Node; import org.gephi.graph.api.UndirectedSubgraph; import org.gephi.graph.impl.EdgeStore.EdgeInOutIterator; @@ -38,8 +39,8 @@ public class GraphViewImpl implements GraphView { protected final boolean nodeView; protected final boolean edgeView; protected final GraphAttributesImpl attributes; - protected BitVector nodeBitVector; - protected BitVector edgeBitVector; + protected BitSet nodeBitVector; + protected BitSet edgeBitVector; protected int storeId; // Version protected final GraphVersion version; @@ -62,11 +63,11 @@ public GraphViewImpl(final GraphStore store, boolean nodes, boolean edges) { this.edgeView = edges; this.attributes = new GraphAttributesImpl(); if (nodes) { - this.nodeBitVector = new BitVector(store.nodeStore.maxStoreId()); + this.nodeBitVector = new BitSet(store.nodeStore.maxStoreId()); } else { this.nodeBitVector = null; } - this.edgeBitVector = new BitVector(store.edgeStore.maxStoreId()); + this.edgeBitVector = new BitSet(store.edgeStore.maxStoreId()); this.typeCounts = new int[GraphStoreConfiguration.VIEW_DEFAULT_TYPE_COUNT]; this.mutualEdgeTypeCounts = new int[GraphStoreConfiguration.VIEW_DEFAULT_TYPE_COUNT]; @@ -77,23 +78,71 @@ public GraphViewImpl(final GraphStore store, boolean nodes, boolean edges) { this.interval = Interval.INFINITY_INTERVAL; } + public GraphViewImpl(final GraphStore store, Predicate nodePredicate, Predicate edgePredicate) { + this(store, nodePredicate != null, edgePredicate != null); + + // Fill - optimized with iterators and manual counting + if (nodePredicate != null) { + int count = 0; + for (Node node : graphStore.nodeStore) { + if (nodePredicate.test(node)) { + nodeBitVector.set(node.getStoreId()); + count++; + } + } + nodeCount = count; + incrementNodeVersion(); + } + + // Process edges with iterator + int count = 0; + for (Edge edge : graphStore.edgeStore) { + // Cache store IDs + int sourceId = edge.getSource().getStoreId(); + int targetId = edge.getTarget().getStoreId(); + + // Filter by node predicate if needed + if (nodePredicate != null && (!nodeBitVector.get(sourceId) || !nodeBitVector.get(targetId))) { + continue; + } + + // Filter by edge predicate if needed + if (edgePredicate != null && !edgePredicate.test(edge)) { + continue; + } + + edgeBitVector.set(edge.getStoreId()); + int type = edge.getType(); + typeCounts[type]++; + count++; + + if (((EdgeImpl) edge).isMutual() && !edge.isSelfLoop() && containsEdge(graphStore.edgeStore + .get(edge.getTarget(), edge.getSource(), type, false))) { + mutualEdgeTypeCounts[type]++; + mutualEdgesCount++; + } + } + edgeCount = count; + } + public GraphViewImpl(final GraphViewImpl view, boolean nodes, boolean edges) { this.graphStore = view.graphStore; this.nodeView = nodes; this.edgeView = edges; this.attributes = new GraphAttributesImpl(); if (nodes) { - this.nodeBitVector = view.nodeBitVector.copy(); + this.nodeBitVector = (BitSet) view.nodeBitVector.clone(); this.nodeCount = view.nodeCount; } else { this.nodeBitVector = null; } this.edgeCount = view.edgeCount; - this.edgeBitVector = view.edgeBitVector.copy(); + this.edgeBitVector = (BitSet) view.edgeBitVector.clone(); this.typeCounts = new int[view.typeCounts.length]; System.arraycopy(view.typeCounts, 0, typeCounts, 0, view.typeCounts.length); this.mutualEdgeTypeCounts = new int[view.mutualEdgeTypeCounts.length]; System.arraycopy(view.mutualEdgeTypeCounts, 0, mutualEdgeTypeCounts, 0, view.mutualEdgeTypeCounts.length); + this.mutualEdgesCount = view.mutualEdgesCount; this.directedDecorator = new GraphViewDecorator(graphStore, this, false); this.undirectedDecorator = new GraphViewDecorator(graphStore, this, true); this.version = graphStore.version != null ? new GraphVersion(directedDecorator) : null; @@ -133,7 +182,7 @@ public boolean addNode(final Node node) { if (nodeView && !edgeView) { // Add edges - EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node); + EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node, false); while (itr.hasNext()) { EdgeImpl edge = itr.next(); NodeImpl opposite = edge.source == nodeImpl ? edge.target : edge.source; @@ -142,9 +191,6 @@ public boolean addNode(final Node node) { int edgeid = edge.storeId; boolean edgeisSet = edgeBitVector.get(edgeid); if (!edgeisSet) { - - incrementEdgeVersion(); - addEdge(edge); } // End @@ -232,7 +278,7 @@ public boolean removeNode(final Node node) { } // Remove edges - EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node); + EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node, false); while (itr.hasNext()) { EdgeImpl edgeImpl = itr.next(); @@ -263,6 +309,66 @@ public boolean removeNodeAll(final Collection nodes) { return false; } + public boolean retainNodes(final Collection c) { + if (nodeView) { + if (!c.isEmpty()) { + // Build BitSet of nodes to retain + BitSet retainSet = new BitSet(graphStore.nodeStore.maxStoreId()); + for (Node o : c) { + checkValidNodeObject(o); + retainSet.set(o.getStoreId()); + } + + // Find nodes to remove: nodes in this view but NOT in retain set + // This is equivalent to: nodeBitVector AND NOT retainSet + BitSet nodesToRemove = (BitSet) nodeBitVector.clone(); + nodesToRemove.andNot(retainSet); + + if (nodesToRemove.isEmpty()) { + return false; + } + + // Bulk remove nodes + bulkRemoveNodes(nodesToRemove); + return true; + } else if (nodeCount != 0) { + clear(); + return true; + } + } + return false; + } + + public boolean retainEdges(final Collection c) { + if (edgeView) { + if (!c.isEmpty()) { + // Build BitSet of edges to retain + BitSet retainSet = new BitSet(graphStore.edgeStore.maxStoreId()); + for (Edge o : c) { + checkValidEdgeObject(o); + retainSet.set(o.getStoreId()); + } + + // Find edges to remove: edges in this view but NOT in retain set + // This is equivalent to: edgeBitVector AND NOT retainSet + BitSet edgesToRemove = (BitSet) edgeBitVector.clone(); + edgesToRemove.andNot(retainSet); + + if (edgesToRemove.isEmpty()) { + return false; + } + + // Bulk remove edges + bulkRemoveEdges(edgesToRemove); + return true; + } else if (edgeCount != 0) { + clearEdges(); + return true; + } + } + return false; + } + public boolean removeEdge(final Edge edge) { checkEdgeView(); @@ -356,24 +462,15 @@ public void clearEdges() { public void fill() { if (nodeView) { - if (nodeCount > 0) { - nodeBitVector = new BitVector(graphStore.nodeStore.maxStoreId()); - } - nodeBitVector.not(); + nodeBitVector.set(0, graphStore.nodeStore.maxStoreId(), true); this.nodeCount = graphStore.nodeStore.size(); } - if (edgeCount > 0) { - edgeBitVector = new BitVector(graphStore.edgeStore.maxStoreId()); - } - edgeBitVector.not(); + edgeBitVector.set(0, graphStore.edgeStore.maxStoreId()); this.edgeCount = graphStore.edgeStore.size(); - int typeLength = graphStore.edgeStore.longDictionary.length; + int typeLength = graphStore.edgeStore.typeSize.length; this.typeCounts = new int[typeLength]; - for (int i = 0; i < typeLength; i++) { - int count = graphStore.edgeStore.longDictionary[i].size(); - this.typeCounts[i] = count; - } + System.arraycopy(graphStore.edgeStore.typeSize, 0, this.typeCounts, 0, typeLength); this.mutualEdgeTypeCounts = new int[graphStore.edgeStore.mutualEdgesTypeSize.length]; System.arraycopy(graphStore.edgeStore.mutualEdgesTypeSize, 0, this.mutualEdgeTypeCounts, 0, this.mutualEdgeTypeCounts.length); this.mutualEdgesCount = graphStore.edgeStore.mutualEdgesSize; @@ -405,102 +502,138 @@ public void fill() { } } - public boolean containsNode(final NodeImpl node) { + public boolean containsNode(final Node node) { if (!nodeView) { return true; } - return nodeBitVector.get(node.storeId); + return nodeBitVector.get(node.getStoreId()); } - public boolean containsEdge(final EdgeImpl edge) { - return edgeBitVector.get(edge.storeId); + public boolean containsEdge(final Edge edge) { + return edgeBitVector.get(edge.getStoreId()); } public void intersection(final GraphViewImpl otherView) { - BitVector nodeOtherBitVector = otherView.nodeBitVector; - BitVector edgeOtherBitVector = otherView.edgeBitVector; + BitSet nodeOtherBitVector = otherView.nodeBitVector; + BitSet edgeOtherBitVector = otherView.edgeBitVector; if (nodeView) { - int nodeSize = nodeBitVector.size(); - for (int i = 0; i < nodeSize; i++) { - boolean t = nodeBitVector.get(i); - boolean o = nodeOtherBitVector.get(i); - if (t && !o) { - removeNode(getNode(i)); - } + // Find nodes to remove: nodes in this view but NOT in other view + BitSet nodesToRemove = (BitSet) nodeBitVector.clone(); + nodesToRemove.andNot(nodeOtherBitVector); + + if (!nodesToRemove.isEmpty()) { + // Bulk remove nodes + bulkRemoveNodes(nodesToRemove); } } if (edgeView) { - int edgeSize = edgeBitVector.size(); - for (int i = 0; i < edgeSize; i++) { - boolean t = edgeBitVector.get(i); - boolean o = edgeOtherBitVector.get(i); - if (t && !o) { - removeEdge(getEdge(i)); - } + // Find edges to remove: edges in this view but NOT in other view + BitSet edgesToRemove = (BitSet) edgeBitVector.clone(); + edgesToRemove.andNot(edgeOtherBitVector); + + if (!edgesToRemove.isEmpty()) { + // Bulk remove edges + bulkRemoveEdges(edgesToRemove); } } } public void union(final GraphViewImpl otherView) { - BitVector nodeOtherBitVector = otherView.nodeBitVector; - BitVector edgeOtherBitVector = otherView.edgeBitVector; + BitSet nodeOtherBitVector = otherView.nodeBitVector; + BitSet edgeOtherBitVector = otherView.edgeBitVector; if (nodeView) { - int nodeSize = nodeBitVector.size(); - for (int i = 0; i < nodeSize; i++) { - boolean t = nodeBitVector.get(i); - boolean o = nodeOtherBitVector.get(i); - if (!t && o) { - addNode(getNode(i)); - } + // Find nodes to add: nodes in other view but NOT in this view + BitSet nodesToAdd = (BitSet) nodeOtherBitVector.clone(); + nodesToAdd.andNot(nodeBitVector); + + if (!nodesToAdd.isEmpty()) { + // Bulk add nodes + bulkAddNodes(nodesToAdd); } } if (edgeView) { - int edgeSize = edgeBitVector.size(); - for (int i = 0; i < edgeSize; i++) { - boolean t = edgeBitVector.get(i); - boolean o = edgeOtherBitVector.get(i); - if (!t && o) { - addEdge(getEdge(i)); - } + // Find edges to add: edges in other view but NOT in this view + BitSet edgesToAdd = (BitSet) edgeOtherBitVector.clone(); + edgesToAdd.andNot(edgeBitVector); + + if (!edgesToAdd.isEmpty()) { + // Bulk add edges + bulkAddEdges(edgesToAdd); } } } public void not() { + // Flip node bits if this is a node view if (nodeView) { - nodeBitVector.not(); + nodeBitVector.flip(0, graphStore.nodeStore.maxStoreId()); this.nodeCount = graphStore.nodeStore.size() - this.nodeCount; + incrementNodeVersion(); } - edgeBitVector.not(); + // Flip edge bits + edgeBitVector.flip(0, graphStore.edgeStore.maxStoreId()); + + // Update edge counts by subtracting from totals this.edgeCount = graphStore.edgeStore.size() - this.edgeCount; - for (int i = 0; i < typeCounts.length; i++) { - this.typeCounts[i] = graphStore.edgeStore.longDictionary[i].size() - this.typeCounts[i]; + + // Ensure type count arrays are sized to match the store + int storeTypeLength = graphStore.edgeStore.longDictionary.length; + if (typeCounts.length < storeTypeLength) { + int[] newTypeCounts = new int[storeTypeLength]; + System.arraycopy(typeCounts, 0, newTypeCounts, 0, typeCounts.length); + typeCounts = newTypeCounts; + + int[] newMutualCounts = new int[storeTypeLength]; + System.arraycopy(mutualEdgeTypeCounts, 0, newMutualCounts, 0, mutualEdgeTypeCounts.length); + mutualEdgeTypeCounts = newMutualCounts; + } + + // Invert all type counts (including types that weren't in the view before) + for (int i = 0; i < storeTypeLength; i++) { + this.typeCounts[i] = graphStore.edgeStore.typeSize[i] - this.typeCounts[i]; } - for (int i = 0; i < mutualEdgeTypeCounts.length; i++) { + for (int i = 0; i < graphStore.edgeStore.mutualEdgesTypeSize.length; i++) { this.mutualEdgeTypeCounts[i] = graphStore.edgeStore.mutualEdgesTypeSize[i] - this.mutualEdgeTypeCounts[i]; } this.mutualEdgesCount = graphStore.edgeStore.mutualEdgesSize - this.mutualEdgesCount; - if (nodeView) { - incrementNodeVersion(); - } incrementEdgeVersion(); + // If node view is enabled, remove edges with invalid endpoints + // Optimization: Only iterate through edges that are NOW in the view (after + // flip) + // instead of all edges in the store if (nodeView) { - for (Edge e : graphStore.edgeStore) { - boolean t = edgeBitVector.get(e.getStoreId()); - if (t && (!nodeBitVector.get(e.getSource().getStoreId()) || !nodeBitVector.get(e.getTarget() - .getStoreId()))) { - removeEdge((EdgeImpl) e); + BitSet edgesToRemove = new BitSet(); + + // Iterate only over edges that are set in the view (much faster for sparse + // views) + for (int edgeId = edgeBitVector.nextSetBit(0); edgeId >= 0; edgeId = edgeBitVector.nextSetBit(edgeId + 1)) { + EdgeImpl edge = getEdge(edgeId); + if (edge == null) { + // SAFETY: Edge no longer exists in store + edgesToRemove.set(edgeId); + continue; + } + // Check if both endpoints are in the node view + if (!nodeBitVector.get(edge.source.storeId) || !nodeBitVector.get(edge.target.storeId)) { + edgesToRemove.set(edgeId); } } + + // Bulk remove invalid edges + if (!edgesToRemove.isEmpty()) { + bulkRemoveEdgesForNot(edgesToRemove); + } } + // Rebuild indexes (necessary for NOT operation as the view content has + // completely changed) if (nodeView) { IndexStore nodeIndexStore = graphStore.nodeTable.store.indexStore; if (nodeIndexStore != null) { @@ -533,6 +666,250 @@ public void addEdgeInNodeView(EdgeImpl edge) { } } + /** + * Bulk remove nodes from the view. This is more efficient than removing nodes one by one as it batches index + * updates and increments version only once. + */ + private void bulkRemoveNodes(BitSet nodesToRemove) { + // First pass: collect all edges to remove (incident to removed nodes) + BitSet edgesToRemove = new BitSet(); + for (int nodeId = nodesToRemove.nextSetBit(0); nodeId >= 0; nodeId = nodesToRemove.nextSetBit(nodeId + 1)) { + NodeImpl node = getNode(nodeId); + if (node == null) { + continue; // SAFETY: Node was removed from store (storeId reused or deleted) + } + + EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node, false); + while (itr.hasNext()) { + EdgeImpl edge = itr.next(); + int edgeId = edge.storeId; + if (edgeBitVector.get(edgeId)) { + edgesToRemove.set(edgeId); + } + } + } + + // Remove edges in bulk + if (!edgesToRemove.isEmpty()) { + bulkRemoveEdges(edgesToRemove); + } + + // Update node bit vector + int removedCount = nodesToRemove.cardinality(); + nodeBitVector.andNot(nodesToRemove); + nodeCount -= removedCount; + incrementNodeVersion(); + + // Bulk update indexes + IndexStore indexStore = graphStore.nodeTable.store.indexStore; + TimeIndexStore timeIndexStore = graphStore.timeStore.nodeIndexStore; + + if (indexStore != null || timeIndexStore != null) { + for (int i = nodesToRemove.nextSetBit(0); i >= 0; i = nodesToRemove.nextSetBit(i + 1)) { + NodeImpl node = getNode(i); + if (node != null) { // SAFETY: Skip if node no longer exists + if (indexStore != null) { + indexStore.clearInView(node, this); + } + if (timeIndexStore != null) { + timeIndexStore.clearInView(node, this); + } + } + } + } + } + + /** + * Bulk add nodes to the view. This is more efficient than adding nodes one by one as it batches index updates and + * increments version only once. + */ + private void bulkAddNodes(BitSet nodesToAdd) { + // Update node bit vector + int addedCount = nodesToAdd.cardinality(); + nodeBitVector.or(nodesToAdd); + nodeCount += addedCount; + incrementNodeVersion(); + + // Bulk update indexes + IndexStore indexStore = graphStore.nodeTable.store.indexStore; + TimeIndexStore timeIndexStore = graphStore.timeStore.nodeIndexStore; + + if (indexStore != null || timeIndexStore != null) { + for (int i = nodesToAdd.nextSetBit(0); i >= 0; i = nodesToAdd.nextSetBit(i + 1)) { + NodeImpl node = getNode(i); + if (node != null) { // SAFETY: Skip if node no longer exists + if (indexStore != null) { + indexStore.indexInView(node, this); + } + if (timeIndexStore != null) { + timeIndexStore.indexInView(node, this); + } + } + } + } + + // If nodeView && !edgeView, add edges between newly added nodes and existing + // nodes + if (nodeView && !edgeView) { + BitSet edgesToAdd = new BitSet(); + for (int nodeId = nodesToAdd.nextSetBit(0); nodeId >= 0; nodeId = nodesToAdd.nextSetBit(nodeId + 1)) { + NodeImpl node = getNode(nodeId); + if (node == null) { + continue; // SAFETY: Skip if node no longer exists + } + + EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node, false); + while (itr.hasNext()) { + EdgeImpl edge = itr.next(); + NodeImpl opposite = edge.source == node ? edge.target : edge.source; + // Check if opposite node is in view and edge is not already in view + if (nodeBitVector.get(opposite.storeId) && !edgeBitVector.get(edge.storeId)) { + edgesToAdd.set(edge.storeId); + } + } + } + + if (!edgesToAdd.isEmpty()) { + bulkAddEdges(edgesToAdd); + } + } + } + + /** + * Bulk remove edges from the view. This is more efficient than removing edges one by one as it updates stats in + * bulk and increments version only once. + */ + private void bulkRemoveEdges(BitSet edgesToRemove) { + // Update edge bit vector + int removedCount = edgesToRemove.cardinality(); + edgeBitVector.andNot(edgesToRemove); + edgeCount -= removedCount; + + // Update type counts and mutual edge counts + for (int i = edgesToRemove.nextSetBit(0); i >= 0; i = edgesToRemove.nextSetBit(i + 1)) { + EdgeImpl edge = getEdge(i); + if (edge == null) { + continue; // SAFETY: Edge was removed from store (storeId reused or deleted) + } + + int type = edge.type; + ensureTypeCountArrayCapacity(type); + typeCounts[type]--; + + if (edge.isMutual() && !edge.isSelfLoop()) { + EdgeImpl reverseEdge = graphStore.edgeStore.get(edge.target, edge.source, edge.type, false); + if (reverseEdge != null && containsEdge(reverseEdge)) { + mutualEdgeTypeCounts[type]--; + mutualEdgesCount--; + } + } + } + + incrementEdgeVersion(); + + // Bulk update indexes + IndexStore indexStore = graphStore.edgeTable.store.indexStore; + TimeIndexStore timeIndexStore = graphStore.timeStore.edgeIndexStore; + + if (indexStore != null || timeIndexStore != null) { + for (int i = edgesToRemove.nextSetBit(0); i >= 0; i = edgesToRemove.nextSetBit(i + 1)) { + EdgeImpl edge = getEdge(i); + if (edge != null) { // SAFETY: Skip if edge no longer exists + if (indexStore != null) { + indexStore.clearInView(edge, this); + } + if (timeIndexStore != null) { + timeIndexStore.clearInView(edge, this); + } + } + } + } + } + + /** + * Bulk add edges to the view. This is more efficient than adding edges one by one as it updates stats in bulk and + * increments version only once. + */ + private void bulkAddEdges(BitSet edgesToAdd) { + // Update edge bit vector + int addedCount = edgesToAdd.cardinality(); + edgeBitVector.or(edgesToAdd); + edgeCount += addedCount; + + // Update type counts and mutual edge counts + for (int i = edgesToAdd.nextSetBit(0); i >= 0; i = edgesToAdd.nextSetBit(i + 1)) { + EdgeImpl edge = getEdge(i); + if (edge == null) { + continue; // SAFETY: Skip if edge no longer exists in store + } + + int type = edge.type; + ensureTypeCountArrayCapacity(type); + typeCounts[type]++; + + if (edge.isMutual() && !edge.isSelfLoop()) { + EdgeImpl reverseEdge = graphStore.edgeStore.get(edge.target, edge.source, edge.type, false); + if (reverseEdge != null && containsEdge(reverseEdge)) { + mutualEdgeTypeCounts[type]++; + mutualEdgesCount++; + } + } + } + + incrementEdgeVersion(); + + // Bulk update indexes + IndexStore indexStore = graphStore.edgeTable.store.indexStore; + TimeIndexStore timeIndexStore = graphStore.timeStore.edgeIndexStore; + + if (indexStore != null || timeIndexStore != null) { + for (int i = edgesToAdd.nextSetBit(0); i >= 0; i = edgesToAdd.nextSetBit(i + 1)) { + EdgeImpl edge = getEdge(i); + if (edge != null) { // SAFETY: Skip if edge no longer exists + if (indexStore != null) { + indexStore.indexInView(edge, this); + } + if (timeIndexStore != null) { + timeIndexStore.indexInView(edge, this); + } + } + } + } + } + + /** + * Special bulk remove for the not() operation. This updates the bit vector and stats but does NOT increment version + * or update indexes (those are handled separately in not()). + */ + private void bulkRemoveEdgesForNot(BitSet edgesToRemove) { + // Update edge bit vector + int removedCount = edgesToRemove.cardinality(); + edgeBitVector.andNot(edgesToRemove); + edgeCount -= removedCount; + + // Update type counts and mutual edge counts + for (int i = edgesToRemove.nextSetBit(0); i >= 0; i = edgesToRemove.nextSetBit(i + 1)) { + EdgeImpl edge = getEdge(i); + if (edge == null) { + continue; // SAFETY: Skip if edge no longer exists in store + } + + int type = edge.type; + ensureTypeCountArrayCapacity(type); + typeCounts[type]--; + + if (edge.isMutual() && !edge.isSelfLoop()) { + EdgeImpl reverseEdge = graphStore.edgeStore.get(edge.target, edge.source, edge.type, false); + if (reverseEdge != null && containsEdge(reverseEdge)) { + mutualEdgeTypeCounts[type]--; + mutualEdgesCount--; + } + } + } + // Note: Version increment and index updates are handled by the caller (not() + // method) + } + public int getNodeCount() { if (nodeView) { return nodeCount; @@ -599,6 +976,10 @@ public boolean isDestroyed() { return storeId == GraphViewStore.NULL_VIEW; } + protected int getVersion() { + return Objects.hash(version.nodeVersion, version.edgeVersion); + } + protected GraphObserverImpl createGraphObserver(Graph graph, boolean withDiff) { if (observers != null) { GraphObserverImpl observer = new GraphObserverImpl(graphStore, version, graph, withDiff); @@ -625,33 +1006,22 @@ protected void destroyAllObservers() { } } - protected void ensureNodeVectorSize(NodeImpl node) { - int sid = node.storeId; - if (sid >= nodeBitVector.size()) { - int newSize = Math - .min(Math.max(sid + 1, (int) (sid * GraphStoreConfiguration.VIEW_GROWING_FACTOR)), Integer.MAX_VALUE); - nodeBitVector = growBitVector(nodeBitVector, newSize); - } - } - - private void ensureNodeVectorSize(int size) { - if (size > nodeBitVector.size()) { - nodeBitVector = growBitVector(nodeBitVector, size); - } - } + protected void setEdgeType(EdgeImpl edgeImpl, int oldType, boolean wasMutual) { + ensureTypeCountArrayCapacity(edgeImpl.type); + typeCounts[oldType]--; + typeCounts[edgeImpl.type]++; - private void ensureEdgeVectorSize(int size) { - if (size > edgeBitVector.size()) { - edgeBitVector = growBitVector(edgeBitVector, size); - } - } + if (!edgeImpl.isSelfLoop()) { + if (wasMutual && containsEdge(graphStore.edgeStore.get(edgeImpl.target, edgeImpl.source, oldType, false))) { + mutualEdgeTypeCounts[oldType]--; + mutualEdgesCount--; + } - protected void ensureEdgeVectorSize(EdgeImpl edge) { - int sid = edge.storeId; - if (sid >= edgeBitVector.size()) { - int newSize = Math - .min(Math.max(sid + 1, (int) (sid * GraphStoreConfiguration.VIEW_GROWING_FACTOR)), Integer.MAX_VALUE); - edgeBitVector = growBitVector(edgeBitVector, newSize); + if (edgeImpl.isMutual() && containsEdge(graphStore.edgeStore + .get(edgeImpl.target, edgeImpl.source, edgeImpl.type, false))) { + mutualEdgeTypeCounts[edgeImpl.type]++; + mutualEdgesCount++; + } } } @@ -666,7 +1036,8 @@ private void addEdge(EdgeImpl edgeImpl) { typeCounts[type]++; - if (edgeImpl.isMutual() && edgeImpl.source.storeId < edgeImpl.target.storeId) { + if (edgeImpl.isMutual() && !edgeImpl.isSelfLoop() && containsEdge(graphStore.edgeStore + .get(edgeImpl.target, edgeImpl.source, edgeImpl.type, false))) { mutualEdgeTypeCounts[type]++; mutualEdgesCount++; } @@ -688,7 +1059,8 @@ private void removeEdge(EdgeImpl edgeImpl) { edgeCount--; typeCounts[edgeImpl.type]--; - if (edgeImpl.isMutual() && edgeImpl.source.storeId < edgeImpl.target.storeId) { + if (edgeImpl.isMutual() && !edgeImpl.isSelfLoop() && containsEdge(graphStore.edgeStore + .get(edgeImpl.target, edgeImpl.source, edgeImpl.type, false))) { mutualEdgeTypeCounts[edgeImpl.type]--; mutualEdgesCount--; } @@ -697,13 +1069,10 @@ private void removeEdge(EdgeImpl edgeImpl) { if (indexStore != null) { indexStore.clearInView(edgeImpl, this); } - } - - private BitVector growBitVector(BitVector bitVector, int size) { - long[] elements = bitVector.elements(); - long[] newElements = QuickBitVector.makeBitVector(size, 1); - System.arraycopy(elements, 0, newElements, 0, elements.length); - return new BitVector(newElements, size); + TimeIndexStore timeIndexStore = graphStore.timeStore.edgeIndexStore; + if (timeIndexStore != null) { + timeIndexStore.clearInView(edgeImpl, this); + } } private NodeImpl getNode(int id) { @@ -833,7 +1202,7 @@ private void checkValidNodeObject(final Node n) { if (!(n instanceof NodeImpl)) { throw new ClassCastException("Object must be a NodeImpl object"); } - if (((NodeImpl) n).storeId == NodeStore.NULL_ID) { + if (n.getStoreId() == NodeStore.NULL_ID) { throw new IllegalArgumentException("Node should belong to a store"); } } diff --git a/store/src/main/java/org/gephi/graph/impl/GraphViewStore.java b/src/main/java/org/gephi/graph/impl/GraphViewStore.java similarity index 81% rename from store/src/main/java/org/gephi/graph/impl/GraphViewStore.java rename to src/main/java/org/gephi/graph/impl/GraphViewStore.java index 599b485e..4df43564 100644 --- a/store/src/main/java/org/gephi/graph/impl/GraphViewStore.java +++ b/src/main/java/org/gephi/graph/impl/GraphViewStore.java @@ -17,11 +17,12 @@ import it.unimi.dsi.fastutil.ints.IntRBTreeSet; import it.unimi.dsi.fastutil.ints.IntSortedSet; -import org.gephi.graph.api.Interval; +import java.util.function.Predicate; import org.gephi.graph.api.DirectedSubgraph; import org.gephi.graph.api.Edge; import org.gephi.graph.api.Graph; import org.gephi.graph.api.GraphView; +import org.gephi.graph.api.Interval; import org.gephi.graph.api.Node; import org.gephi.graph.api.Subgraph; import org.gephi.graph.api.UndirectedSubgraph; @@ -54,6 +55,17 @@ public GraphViewImpl createView() { return createView(true, true); } + public GraphViewImpl createView(Predicate nodeFilter, Predicate edgeFilter) { + graphStore.autoWriteLock(); + try { + GraphViewImpl graphView = new GraphViewImpl(graphStore, nodeFilter, edgeFilter); + addView(graphView); + return graphView; + } finally { + graphStore.autoWriteUnlock(); + } + } + public GraphViewImpl createView(boolean nodes, boolean edges) { graphStore.autoWriteLock(); try { @@ -70,6 +82,7 @@ public GraphViewImpl createView(GraphView view) { } public GraphViewImpl createView(GraphView view, boolean nodes, boolean edges) { + checkNonNullViewObject(view); if (view.isMainView()) { graphStore.autoWriteLock(); try { @@ -81,7 +94,7 @@ public GraphViewImpl createView(GraphView view, boolean nodes, boolean edges) { graphStore.autoWriteUnlock(); } } else { - checkNonNullViewObject(view); + checkGraphViewObject(view); checkViewExist((GraphViewImpl) view); graphStore.autoWriteLock(); @@ -96,10 +109,15 @@ public GraphViewImpl createView(GraphView view, boolean nodes, boolean edges) { } public void destroyView(GraphView view) { + checkNonNullViewObject(view); + if (view.isMainView()) { + throw new IllegalArgumentException("Can't delete the main view"); + } + checkGraphViewObject(view); + checkViewExist((GraphViewImpl) view); + graphStore.autoWriteLock(); try { - checkNonNullViewObject(view); - TimeIndexStore nodeTimeStore = graphStore.timeStore.nodeIndexStore; if (nodeTimeStore != null) { nodeTimeStore.deleteViewIndex(((GraphViewImpl) view).getDirectedGraph()); @@ -143,6 +161,7 @@ public boolean contains(GraphView view) { graphStore.autoReadLock(); try { checkNonNullViewObject(view); + checkGraphViewObject(view); GraphViewImpl viewImpl = (GraphViewImpl) view; int id = viewImpl.storeId; if (id != NULL_VIEW && id < length && views[id] == view) { @@ -160,6 +179,7 @@ public int size() { public Subgraph getGraph(GraphView view) { checkNonNullViewObject(view); + checkGraphViewObject(view); if (graphStore.isUndirected()) { if (view.isMainView()) { @@ -176,6 +196,7 @@ public Subgraph getGraph(GraphView view) { public DirectedSubgraph getDirectedGraph(GraphView view) { checkNonNullViewObject(view); + checkGraphViewObject(view); if (view.isMainView()) { return graphStore; @@ -187,6 +208,7 @@ public DirectedSubgraph getDirectedGraph(GraphView view) { public UndirectedSubgraph getUndirectedGraph(GraphView view) { checkNonNullViewObject(view); + checkGraphViewObject(view); if (view.isMainView()) { return graphStore.undirectedDecorator; @@ -202,7 +224,7 @@ public void setVisibleView(GraphView view) { if (view == null || view == graphStore.mainGraphView) { visibleView = graphStore.mainGraphView; } else { - checkNonNullViewObject(view); + checkGraphViewObject(view); checkViewExist((GraphViewImpl) view); visibleView = view; } @@ -217,51 +239,45 @@ public GraphObserverImpl createGraphObserver(Graph graph, boolean withDiff) { public void destroyGraphObserver(GraphObserverImpl graphObserver) { GraphViewImpl graphViewImpl = (GraphViewImpl) graphObserver.graph.getView(); - checkViewExist(graphViewImpl); + if (graphObserver.graphStore != this.graphStore) { + throw new RuntimeException("This observer doesn't belong to this store"); + } graphViewImpl.destroyGraphObserver(graphObserver); } - protected void addNode(NodeImpl node) { - if (views.length > 0) { - for (GraphViewImpl view : views) { - if (view != null) { - view.ensureNodeVectorSize(node); - } + protected void removeNode(NodeImpl node) { + for (GraphViewImpl view : views) { + if (view != null) { + view.removeNode(node); } } } - protected void removeNode(NodeImpl node) { - if (views.length > 0) { - for (GraphViewImpl view : views) { - if (view != null) { - view.removeNode(node); + protected void addEdge(EdgeImpl edge) { + for (GraphViewImpl view : views) { + if (view != null) { + if (view.nodeView && !view.edgeView) { + view.addEdgeInNodeView(edge); } } } } - protected void addEdge(EdgeImpl edge) { - if (views.length > 0) { - for (GraphViewImpl view : views) { - if (view != null) { - view.ensureEdgeVectorSize(edge); - - if (view.nodeView && !view.edgeView) { - view.addEdgeInNodeView(edge); - } + protected void setEdgeType(EdgeImpl edge, int oldType, boolean wasMutual) { + for (GraphViewImpl view : views) { + if (view != null) { + if ((view.nodeView && !view.edgeView) || (view.edgeView && view.containsEdge(edge))) { + view.setEdgeType(edge, oldType, wasMutual); } } } } protected void removeEdge(EdgeImpl edge) { - if (views.length > 0) { - for (GraphViewImpl view : views) { - if (view != null) { - view.removeEdge(edge); - } + for (GraphViewImpl view : views) { + if (view != null) { + view.removeEdge(edge); } } } @@ -309,7 +325,7 @@ private void ensureArraySize(int index) { public int deepHashCode() { int hash = 5; for (GraphViewImpl view : this.views) { - hash = 67 * hash + view.deepHashCode(); + hash = 67 * hash + (view != null ? view.deepHashCode() : 0); } hash = 67 * hash + this.length; return hash; @@ -344,14 +360,15 @@ public boolean deepEquals(GraphViewStore obj) { return true; } - protected void checkNonNullViewObject(final Object o) { + protected void checkNonNullViewObject(final GraphView o) { if (o == null) { throw new NullPointerException(); } - if (o != graphStore.mainGraphView) { - if (!(o instanceof GraphViewImpl)) { - throw new ClassCastException("View must be a GraphViewImpl object"); - } + } + + protected void checkGraphViewObject(final GraphView o) { + if (!o.isMainView() && !(o instanceof GraphViewImpl)) { + throw new IllegalArgumentException("The view is not from this implementation"); } } diff --git a/src/main/java/org/gephi/graph/impl/IndexImpl.java b/src/main/java/org/gephi/graph/impl/IndexImpl.java new file mode 100644 index 00000000..bf14e00c --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/IndexImpl.java @@ -0,0 +1,378 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.gephi.graph.impl; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import org.gephi.graph.api.Column; +import org.gephi.graph.api.ColumnIndex; +import org.gephi.graph.api.Element; +import org.gephi.graph.api.Graph; +import org.gephi.graph.api.Index; + +public class IndexImpl implements Index { + + protected final ColumnStore columnStore; + protected final Graph graph; + protected ColumnIndexImpl[] columns; + protected int columnsCount; + + public IndexImpl(ColumnStore columnStore) { + this(columnStore, columnStore.graphStore); + } + + public IndexImpl(ColumnStore columnStore, Graph graph) { + this.columnStore = columnStore; + this.graph = graph; + this.columns = new ColumnIndexImpl[0]; + } + + @Override + public Class getIndexClass() { + return columnStore.elementType; + } + + @Override + public String getIndexName() { + return "index_" + columnStore.elementType.getCanonicalName(); + } + + @Override + public ColumnIndex getColumnIndex(Column column) { + return getIndex(column); + } + + @Override + public int count(Column column, Object value) { + checkNonNullColumnObject(column); + + ColumnIndexImpl index = getIndex(column); + if (index != null) { + return index.count(value); + } + return 0; + } + + public int count(String key, Object value) { + checkNonNullObject(key); + + return count(columnStore.getColumn(key), value); + } + + @Override + public Iterable get(Column column, Object value) { + checkNonNullColumnObject(column); + + ColumnIndexImpl index = getIndex(column); + if (index != null) { + return index.get(value); + } + return Collections.EMPTY_LIST; + } + + public Iterable get(String key, Object value) { + checkNonNullObject(key); + + return get(columnStore.getColumn(key), value); + } + + @Override + public boolean isSortable(Column column) { + checkNonNullColumnObject(column); + + ColumnIndexImpl index = getIndex(column); + if (index != null) { + return index.isSortable(); + } + return false; + } + + @Override + public Number getMinValue(Column column) { + checkNonNullColumnObject(column); + + ColumnIndexImpl index = getIndex(column); + if (index != null) { + return index.getMinValue(); + } + return null; + } + + @Override + public Number getMaxValue(Column column) { + checkNonNullColumnObject(column); + + ColumnIndexImpl index = getIndex(column); + if (index != null) { + return index.getMaxValue(); + } + return null; + } + + public Iterable>> get(Column column) { + checkNonNullColumnObject(column); + + return getIndex(column); + } + + @Override + public Collection values(Column column) { + checkNonNullColumnObject(column); + + ColumnIndexImpl index = getIndex(column); + if (index != null) { + return index.values(); + } + return Collections.EMPTY_LIST; + } + + @Override + public int countValues(Column column) { + checkNonNullColumnObject(column); + ColumnIndexImpl index = getIndex(column); + if (index != null) { + return index.countValues(); + } + return 0; + } + + @Override + public int countElements(Column column) { + checkNonNullColumnObject(column); + ColumnIndexImpl index = getIndex(column); + if (index != null) { + return index.countElements(); + } + return 0; + } + + public Object put(String key, Object value, T element) { + checkNonNullObject(key); + + return put(columnStore.getColumn(key), value, element); + } + + public Object put(Column column, Object value, T element) { + checkNonNullColumnObject(column); + + return getIndex(column).putValue(element, value); + } + + public void remove(String key, Object value, T element) { + checkNonNullObject(key); + + remove(columnStore.getColumn(key), value, element); + } + + public void remove(Column column, Object value, T element) { + checkNonNullColumnObject(column); + + getIndex(column).removeValue(element, value); + } + + public Object set(String key, Object oldValue, Object value, T element) { + checkNonNullObject(key); + + return set(columnStore.getColumn(key), oldValue, value, element); + } + + public Object set(Column column, Object oldValue, Object value, T element) { + checkNonNullColumnObject(column); + + return getIndex(column).replaceValue(element, oldValue, value); + } + + public void clear() { + for (ColumnIndexImpl ai : columns) { + if (ai != null) { + ai.clear(); + } + } + } + + protected void addColumn(ColumnImpl col) { + ensureColumnSize(col.storeId); + ColumnIndexImpl index = createIndex(col); + columns[col.storeId] = index; + columnsCount++; + } + + protected void addAllColumns(ColumnImpl[] cols) { + ensureColumnSize(cols.length); + for (ColumnImpl col : cols) { + ColumnIndexImpl index = createIndex(col); + ensureColumnSize(col.storeId); + columns[col.storeId] = index; + columnsCount++; + } + } + + protected void removeColumn(ColumnImpl col) { + ColumnIndexImpl index = columns[col.storeId]; + index.destroy(); + columns[col.storeId] = null; + columnsCount--; + } + + protected boolean hasColumn(ColumnImpl col) { + int id = col.storeId; + return id != ColumnStore.NULL_ID && columns.length > id && columns[id].getColumn() == col; + } + + protected ColumnIndexImpl getIndex(Column col) { + int id = col.getIndex(); + if (id != ColumnStore.NULL_ID && columns.length > id) { + ColumnIndexImpl index = columns[id]; + if (index != null && index.getColumn() == col) { + return index; + } + } + + // TODO: Make this more robust + if (col.isProperty()) { + DefaultColumnsImpl defaultColumns = columnStore.graphStore.defaultColumns; + if (col == defaultColumns.degreeColumn) { + return new DegreeNoIndexImpl(graph, DegreeNoIndexImpl.DegreeType.DEGREE); + } else if (col == defaultColumns.inDegreeColumn) { + return new DegreeNoIndexImpl(graph, DegreeNoIndexImpl.DegreeType.IN_DEGREE); + } else if (col == defaultColumns.outDegreeColumn) { + return new DegreeNoIndexImpl(graph, DegreeNoIndexImpl.DegreeType.OUT_DEGREE); + } else if (col == defaultColumns.typeColumn) { + return new EdgeTypeNoIndexImpl(graph); + } + } + return null; + } + + protected ColumnIndexImpl getIndex(String key) { + return getIndex(columnStore.getColumn(key)); + } + + protected void destroy() { + for (ColumnIndexImpl ai : columns) { + if (ai != null) { + ai.destroy(); + } + } + columns = new ColumnIndexImpl[0]; + columnsCount = 0; + } + + protected int size() { + return columnsCount; + } + + ColumnIndexImpl createIndex(ColumnImpl col) { + return col.isIndexed() && ColumnStandardIndexImpl.isSupportedType(col) ? createStandardIndex(col) + : createNoIndex(col, graph); + } + + ColumnNoIndexImpl createNoIndex(ColumnImpl column, Graph graph) { + return new ColumnNoIndexImpl(column, graph, columnStore.elementType); + } + + ColumnStandardIndexImpl createStandardIndex(ColumnImpl column) { + if (column.getTypeClass().equals(Byte.class)) { + // Byte + return new ColumnStandardIndexImpl.ByteStandardIndex(column); + } else if (column.getTypeClass().equals(Short.class)) { + // Short + return new ColumnStandardIndexImpl.ShortStandardIndex(column); + } else if (column.getTypeClass().equals(Integer.class)) { + // Integer + return new ColumnStandardIndexImpl.IntegerStandardIndex(column); + } else if (column.getTypeClass().equals(Long.class)) { + // Long + return new ColumnStandardIndexImpl.LongStandardIndex(column); + } else if (column.getTypeClass().equals(Float.class)) { + // Float + return new ColumnStandardIndexImpl.FloatStandardIndex(column); + } else if (column.getTypeClass().equals(Double.class)) { + // Double + return new ColumnStandardIndexImpl.DoubleStandardIndex(column); + } else if (Number.class.isAssignableFrom(column.getTypeClass())) { + // Other numbers + return new ColumnStandardIndexImpl.GenericNumberStandardIndex(column); + } else if (column.getTypeClass().equals(Boolean.class)) { + // Boolean + return new ColumnStandardIndexImpl.BooleanStandardIndex(column); + } else if (column.getTypeClass().equals(Character.class)) { + // Char + return new ColumnStandardIndexImpl.CharStandardIndex(column); + } else if (column.getTypeClass().equals(String.class)) { + // String + return new ColumnStandardIndexImpl.DefaultStandardIndex(column); + } else if (column.getTypeClass().equals(byte[].class)) { + // Byte Array + return new ColumnStandardIndexImpl.ByteArrayStandardIndex(column); + } else if (column.getTypeClass().equals(short[].class)) { + // Short Array + return new ColumnStandardIndexImpl.ShortArrayStandardIndex(column); + } else if (column.getTypeClass().equals(int[].class)) { + // Integer Array + return new ColumnStandardIndexImpl.IntegerArrayStandardIndex(column); + } else if (column.getTypeClass().equals(long[].class)) { + // Long Array + return new ColumnStandardIndexImpl.LongArrayStandardIndex(column); + } else if (column.getTypeClass().equals(float[].class)) { + // Float array + return new ColumnStandardIndexImpl.FloatArrayStandardIndex(column); + } else if (column.getTypeClass().equals(double[].class)) { + // Double array + return new ColumnStandardIndexImpl.DoubleArrayStandardIndex(column); + } else if (column.getTypeClass().equals(boolean[].class)) { + // Boolean array + return new ColumnStandardIndexImpl.BooleanArrayStandardIndex(column); + } else if (column.getTypeClass().equals(char[].class)) { + // Char array + return new ColumnStandardIndexImpl.CharArrayStandardIndex(column); + } else if (column.getTypeClass().equals(String[].class)) { + // String array + return new ColumnStandardIndexImpl.DefaultArrayStandardIndex(column); + } else if (column.getTypeClass().isArray()) { + // Default Array + return new ColumnStandardIndexImpl.DefaultArrayStandardIndex(column); + } + return new ColumnStandardIndexImpl.DefaultStandardIndex(column); + } + + private void ensureColumnSize(int index) { + if (index >= columns.length) { + ColumnIndexImpl[] newArray = new ColumnIndexImpl[index + 1]; + System.arraycopy(columns, 0, newArray, 0, columns.length); + columns = newArray; + } + } + + void checkNonNullObject(final Object o) { + if (o == null) { + throw new NullPointerException(); + } + } + + void checkNonNullColumnObject(final Object o) { + if (o == null) { + throw new NullPointerException(); + } + if (!(o instanceof ColumnImpl)) { + throw new ClassCastException("Must be ColumnImpl object"); + } + } + +} diff --git a/store/src/main/java/org/gephi/graph/impl/IndexStore.java b/src/main/java/org/gephi/graph/impl/IndexStore.java similarity index 72% rename from store/src/main/java/org/gephi/graph/impl/IndexStore.java rename to src/main/java/org/gephi/graph/impl/IndexStore.java index 813bf7a9..f13cff09 100644 --- a/store/src/main/java/org/gephi/graph/impl/IndexStore.java +++ b/src/main/java/org/gephi/graph/impl/IndexStore.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.gephi.graph.impl; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -30,7 +31,7 @@ public class IndexStore { protected final ColumnStore columnStore; - protected final TableLock lock; + protected final TableLockImpl lock; protected final IndexImpl mainIndex; protected final Map> viewIndexes; @@ -41,6 +42,7 @@ public IndexStore(ColumnStore columnStore) { this.lock = columnStore.lock; } + // Table locked protected void addColumn(ColumnImpl col) { mainIndex.addColumn(col); for (IndexImpl index : viewIndexes.values()) { @@ -48,6 +50,7 @@ protected void addColumn(ColumnImpl col) { } } + // Table locked protected void removeColumn(ColumnImpl col) { mainIndex.removeColumn(col); for (IndexImpl index : viewIndexes.values()) { @@ -64,15 +67,12 @@ protected IndexImpl getIndex(Graph graph) { if (view.isMainView()) { return mainIndex; } - lock(); - try { + synchronized (viewIndexes) { IndexImpl viewIndex = viewIndexes.get(graph.getView()); if (viewIndex == null) { viewIndex = createViewIndex(graph); } return viewIndex; - } finally { - unlock(); } } @@ -80,14 +80,19 @@ protected IndexImpl createViewIndex(Graph graph) { if (graph.getView().isMainView()) { throw new IllegalArgumentException("Can't create a view index for the main view"); } - IndexImpl viewIndex = new IndexImpl<>(columnStore); - ColumnImpl[] columns = columnStore.toArray(); - viewIndex.addAllColumns(columns); - viewIndexes.put(graph.getView(), viewIndex); + lock(); + try { + IndexImpl viewIndex = new IndexImpl<>(columnStore, graph); + ColumnImpl[] columns = columnStore.toArray(); + viewIndex.addAllColumns(columns); + viewIndexes.put(graph.getView(), viewIndex); - indexView(graph); + indexView(graph); - return viewIndex; + return viewIndex; + } finally { + unlock(); + } } protected void deleteViewIndex(Graph graph) { @@ -106,26 +111,23 @@ protected void deleteViewIndex(Graph graph) { } public Object set(Column column, Object oldValue, Object value, T element) { - lock(); - try { - value = mainIndex.set(column, oldValue, value, element); + value = mainIndex.set(column, oldValue, value, element); - if (!viewIndexes.isEmpty()) { + if (!viewIndexes.isEmpty()) { + synchronized (viewIndexes) { for (Entry> entry : viewIndexes.entrySet()) { GraphViewImpl graphView = (GraphViewImpl) entry.getKey(); DirectedSubgraph graph = graphView.getDirectedGraph(); - boolean inView = element instanceof Node ? graph.contains((Node) element) : graph - .contains((Edge) element); + boolean inView = element instanceof Node ? graph.contains((Node) element) + : graph.contains((Edge) element); if (inView) { entry.getValue().set(column, oldValue, value, element); } } } - - return value; - } finally { - unlock(); } + + return value; } public void clear(T element) { @@ -137,16 +139,20 @@ public void clear(T element) { final ColumnImpl[] cols = columnStore.columns; for (int i = 0; i < length; i++) { Column c = cols[i]; - if (c != null && c.isIndexed() && elementImpl.attributes.length > c.getIndex()) { - Object value = elementImpl.attributes[c.getIndex()]; + if (c != null) { + Object value = elementImpl.getAttribute(c); mainIndex.remove(c, value, element); - for (Entry> entry : viewIndexes.entrySet()) { - GraphViewImpl graphView = (GraphViewImpl) entry.getKey(); - DirectedSubgraph graph = graphView.getDirectedGraph(); - boolean inView = element instanceof Node ? graph.contains((Node) element) : graph - .contains((Edge) element); - if (inView) { - entry.getValue().remove(c, value, element); + if (!viewIndexes.isEmpty()) { + synchronized (viewIndexes) { + for (Entry> entry : viewIndexes.entrySet()) { + GraphViewImpl graphView = (GraphViewImpl) entry.getKey(); + DirectedSubgraph graph = graphView.getDirectedGraph(); + boolean inView = element instanceof Node ? graph.contains((Node) element) + : graph.contains((Edge) element); + if (inView) { + entry.getValue().remove(c, value, element); + } + } } } } @@ -160,16 +166,15 @@ public void index(T element) { ElementImpl elementImpl = (ElementImpl) element; lock(); try { - ensureAttributeArrayLength(elementImpl, columnStore.length); final int length = columnStore.length; final ColumnImpl[] cols = columnStore.columns; for (int i = 0; i < length; i++) { Column c = cols[i]; - if (c != null && c.isIndexed()) { - Object value = elementImpl.attributes[c.getIndex()]; + if (c != null) { + Object value = elementImpl.getAttribute(c); value = mainIndex.put(c, value, element); - elementImpl.attributes[c.getIndex()] = value; + elementImpl.attributes.setAttribute(c, value); } } } finally { @@ -178,7 +183,7 @@ public void index(T element) { } public void indexView(Graph graph) { - IndexImpl viewIndex = viewIndexes.get(graph.getView()); + final IndexImpl viewIndex = viewIndexes.get(graph.getView()); if (viewIndex != null) { graph.readLock(); try { @@ -192,17 +197,14 @@ public void indexView(Graph graph) { if (iterator != null) { while (iterator.hasNext()) { ElementImpl element = (ElementImpl) iterator.next(); - ensureAttributeArrayLength(element, columnStore.length); final ColumnImpl[] cols = columnStore.columns; - synchronized (element) { - int length = columnStore.length; - for (int i = 0; i < length; i++) { - Column c = cols[i]; - if (c != null && c.isIndexed()) { - Object value = element.attributes[c.getIndex()]; - viewIndex.put(c, value, element); - } + int length = columnStore.length; + for (int i = 0; i < length; i++) { + Column c = cols[i]; + if (c != null) { + Object value = element.getAttribute(c); + viewIndex.put(c, value, element); } } } @@ -223,8 +225,8 @@ public void indexInView(T element, GraphView view) { final ColumnImpl[] cols = columnStore.columns; for (int i = 0; i < length; i++) { Column c = cols[i]; - if (c != null && c.isIndexed()) { - Object value = elementImpl.attributes[c.getIndex()]; + if (c != null) { + Object value = elementImpl.getAttribute(c); index.put(c, value, element); } } @@ -244,8 +246,8 @@ public void clearInView(T element, GraphView view) { final ColumnImpl[] cols = columnStore.columns; for (int i = 0; i < length; i++) { Column c = cols[i]; - if (c != null && c.isIndexed()) { - Object value = elementImpl.attributes[c.getIndex()]; + if (c != null) { + Object value = elementImpl.getAttribute(c); index.remove(c, value, element); } } @@ -279,18 +281,6 @@ public void clear() { } } - private void ensureAttributeArrayLength(ElementImpl element, int size) { - synchronized (element) { - final Object[] attributes = element.attributes; - if (size > attributes.length) { - Object[] newArray = new Object[size]; - System.arraycopy(attributes, 0, newArray, 0, attributes.length); - element.attributes = newArray; - } - } - - } - private void lock() { if (lock != null) { lock.lock(); diff --git a/store/src/main/java/org/gephi/graph/impl/Interval2IntTreeMap.java b/src/main/java/org/gephi/graph/impl/Interval2IntTreeMap.java similarity index 98% rename from store/src/main/java/org/gephi/graph/impl/Interval2IntTreeMap.java rename to src/main/java/org/gephi/graph/impl/Interval2IntTreeMap.java index aeca01c6..61a50982 100644 --- a/store/src/main/java/org/gephi/graph/impl/Interval2IntTreeMap.java +++ b/src/main/java/org/gephi/graph/impl/Interval2IntTreeMap.java @@ -379,8 +379,7 @@ private Node succesor(Node x) { /** * Returns the interval with the lowest left endpoint. * - * @return the interval with the lowest left endpoint or null if the tree is - * empty. + * @return the interval with the lowest left endpoint or null if the tree is empty. */ public Interval minimum() { if (root.left == nil) { @@ -410,8 +409,7 @@ private Interval treeMinimum(Node x) { /** * Returns the interval with the highest right endpoint. * - * @return the interval with the highest right endpoint or null if the tree - * is empty. + * @return the interval with the highest right endpoint or null if the tree is empty. */ public Interval maximum() { if (root.left == nil) { @@ -430,8 +428,7 @@ private Node treeMaximum(Node x) { } /** - * Returns the leftmost point or {@code Double.NEGATIVE_INFINITY} in case of - * no intervals. + * Returns the leftmost point or {@code Double.NEGATIVE_INFINITY} in case of no intervals. * * @return the leftmost point */ @@ -447,8 +444,7 @@ public double getLow() { } /** - * Returns the rightmost point or {@code Double.POSITIVE_INFINITY} in case - * of no intervals. + * Returns the rightmost point or {@code Double.POSITIVE_INFINITY} in case of no intervals. * * @return the rightmost point */ @@ -509,8 +505,7 @@ public Set> entrySet() { } /** - * Returns an entry set of all entries, which interval keys overlap with - * point. + * Returns an entry set of all entries, which interval keys overlap with point. * * @param point point * @return entry set @@ -520,8 +515,7 @@ public Set> entrySet(double point) { } /** - * Returns an entry set of all entries, which interval keys overlap with - * interval. + * Returns an entry set of all entries, which interval keys overlap with interval. * * @param interval interval * @return entry set @@ -655,14 +649,12 @@ private void inorderTreeWalk(Node x, List list) { * Compares this interval tree with the specified object for equality. * *

- * Note that two interval trees are equal if they contain the same - * intervals. + * Note that two interval trees are equal if they contain the same intervals. * * @param obj object to which this interval tree is to be compared * - * @return {@code true} if and only if the specified {@code Object} is a - * {@code IntervalTree} which contain the same intervals as this - * {@code IntervalTree's}. + * @return {@code true} if and only if the specified {@code Object} is a {@code IntervalTree} which contain the same + * intervals as this {@code IntervalTree's}. * * @see #hashCode */ diff --git a/src/main/java/org/gephi/graph/impl/IntervalIndexImpl.java b/src/main/java/org/gephi/graph/impl/IntervalIndexImpl.java new file mode 100644 index 00000000..0ac940c2 --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/IntervalIndexImpl.java @@ -0,0 +1,158 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import java.util.Map; +import org.gephi.graph.api.Element; +import org.gephi.graph.api.ElementIterable; +import org.gephi.graph.api.Interval; +import org.gephi.graph.api.types.IntervalMap; +import org.gephi.graph.api.types.IntervalSet; + +public class IntervalIndexImpl extends TimeIndexImpl> { + + public IntervalIndexImpl(TimeIndexStore> store, boolean main) { + super(store, main); + } + + @Override + public double getMinTimestamp() { + lock(); + try { + Interval2IntTreeMap sortedMap = (Interval2IntTreeMap) timestampIndexStore.timeSortedMap; + if (mainIndex) { + // Returns the minimum across all tracked intervals, including those that + // belong only to dynamic attribute values (IntervalMap columns) and not to + // element existence (IntervalSet). This intentionally gives the earliest time + // at which any graph data exists, which may be earlier than the first time + // any element is present. View indexes filter to element-only intervals. + if (!sortedMap.isEmpty()) { + return sortedMap.getLow(); + } + } else { + if (!sortedMap.isEmpty()) { + for (Map.Entry entry : sortedMap.entrySet()) { + int index = entry.getValue(); + if (index < timestamps.length) { + TimeIndexEntry intervalEntry = timestamps[index]; + if (intervalEntry != null) { + return entry.getKey().getLow(); + } + } + } + } + } + return Double.NEGATIVE_INFINITY; + } finally { + unlock(); + } + } + + @Override + public double getMaxTimestamp() { + lock(); + try { + if (mainIndex) { + // Returns the maximum across all tracked intervals, including those that + // belong only to dynamic attribute values (IntervalMap columns) and not to + // element existence (IntervalSet). See getMinTimestamp() for details. + Interval2IntTreeMap sortedMap = (Interval2IntTreeMap) timestampIndexStore.timeSortedMap; + if (!sortedMap.isEmpty()) { + return sortedMap.getHigh(); + } + } else { + // TODO Better algorithm to find max + Interval2IntTreeMap sortedMap = (Interval2IntTreeMap) timestampIndexStore.timeSortedMap; + if (!sortedMap.isEmpty()) { + double max = Double.NEGATIVE_INFINITY; + boolean found = false; + for (Map.Entry entry : sortedMap.entrySet()) { + int index = entry.getValue(); + if (index < timestamps.length) { + TimeIndexEntry intervalEntry = timestamps[index]; + if (intervalEntry != null) { + found = true; + max = Math.max(max, entry.getKey().getHigh()); + } + } + } + if (found) { + return max; + } + } + + } + return Double.POSITIVE_INFINITY; + } finally { + unlock(); + } + } + + @Override + public ElementIterable get(double timestamp) { + checkDouble(timestamp); + + lock(); + try { + ObjectSet elements = new ObjectOpenHashSet<>(); + Interval2IntTreeMap sortedMap = (Interval2IntTreeMap) timestampIndexStore.timeSortedMap; + if (!sortedMap.isEmpty()) { + for (Integer index : sortedMap.values(timestamp)) { + if (index < timestamps.length) { + TimeIndexEntry ts = timestamps[index]; + if (ts != null) { + elements.addAll(ts.elementSet); + } + } + } + } + if (!elements.isEmpty()) { + return new ElementSetWrapperIterable(elements); + } + return ElementIterable.EMPTY; + } finally { + unlock(); + } + } + + @Override + public ElementIterable get(Interval interval) { + + lock(); + try { + ObjectSet elements = new ObjectOpenHashSet<>(); + Interval2IntTreeMap sortedMap = (Interval2IntTreeMap) timestampIndexStore.timeSortedMap; + if (!sortedMap.isEmpty()) { + for (Integer index : sortedMap.values(interval)) { + if (index < timestamps.length) { + TimeIndexEntry ts = timestamps[index]; + if (ts != null) { + elements.addAll(ts.elementSet); + } + } + } + } + if (!elements.isEmpty()) { + return new ElementSetWrapperIterable(elements); + } + return ElementIterable.EMPTY; + } finally { + unlock(); + } + } +} diff --git a/store/src/main/java/org/gephi/graph/impl/IntervalIndexStore.java b/src/main/java/org/gephi/graph/impl/IntervalIndexStore.java similarity index 94% rename from store/src/main/java/org/gephi/graph/impl/IntervalIndexStore.java rename to src/main/java/org/gephi/graph/impl/IntervalIndexStore.java index c4f103d4..cc194098 100644 --- a/store/src/main/java/org/gephi/graph/impl/IntervalIndexStore.java +++ b/src/main/java/org/gephi/graph/impl/IntervalIndexStore.java @@ -22,7 +22,7 @@ public class IntervalIndexStore extends TimeIndexStore> { - public IntervalIndexStore(Class type, GraphLock lock, boolean indexed) { + public IntervalIndexStore(Class type, TableLockImpl lock, boolean indexed) { super(type, lock, indexed, new Interval2IntTreeMap()); mainIndex = indexed ? new IntervalIndexImpl(this, true) : null; } diff --git a/store/src/main/java/org/gephi/graph/impl/IntervalsParser.java b/src/main/java/org/gephi/graph/impl/IntervalsParser.java similarity index 78% rename from store/src/main/java/org/gephi/graph/impl/IntervalsParser.java rename to src/main/java/org/gephi/graph/impl/IntervalsParser.java index fe28bd58..6ee12828 100644 --- a/store/src/main/java/org/gephi/graph/impl/IntervalsParser.java +++ b/src/main/java/org/gephi/graph/impl/IntervalsParser.java @@ -15,17 +15,21 @@ */ package org.gephi.graph.impl; +import static org.gephi.graph.impl.FormattingAndParsingUtils.COMMA; +import static org.gephi.graph.impl.FormattingAndParsingUtils.EMPTY_VALUE; +import static org.gephi.graph.impl.FormattingAndParsingUtils.LEFT_BOUND_BRACKET; +import static org.gephi.graph.impl.FormattingAndParsingUtils.LEFT_BOUND_SQUARE_BRACKET; +import static org.gephi.graph.impl.FormattingAndParsingUtils.RIGHT_BOUND_BRACKET; +import static org.gephi.graph.impl.FormattingAndParsingUtils.RIGHT_BOUND_SQUARE_BRACKET; + import java.io.IOException; import java.io.StringReader; +import java.time.ZoneId; +import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.List; import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Interval; -import static org.gephi.graph.impl.FormattingAndParsingUtils.COMMA; -import static org.gephi.graph.impl.FormattingAndParsingUtils.LEFT_BOUND_SQUARE_BRACKET; -import static org.gephi.graph.impl.FormattingAndParsingUtils.LEFT_BOUND_BRACKET; -import static org.gephi.graph.impl.FormattingAndParsingUtils.RIGHT_BOUND_SQUARE_BRACKET; -import static org.gephi.graph.impl.FormattingAndParsingUtils.RIGHT_BOUND_BRACKET; import org.gephi.graph.api.types.IntervalBooleanMap; import org.gephi.graph.api.types.IntervalByteMap; import org.gephi.graph.api.types.IntervalCharMap; @@ -37,8 +41,6 @@ import org.gephi.graph.api.types.IntervalSet; import org.gephi.graph.api.types.IntervalShortMap; import org.gephi.graph.api.types.IntervalStringMap; -import org.joda.time.DateTimeZone; -import static org.gephi.graph.impl.FormattingAndParsingUtils.EMPTY_VALUE; /** *

@@ -46,18 +48,16 @@ *

* *

- * The standard format for {@link IntervalMap} is <[start, end, value1]; - * [start, end, value2]>. + * The standard format for {@link IntervalMap} is <[start, end, value1]; [start, end, value2]>. *

* *

- * The standard format for {@link IntervalSet} is <[start, end]; [start, - * end]>. + * The standard format for {@link IntervalSet} is <[start, end]; [start, end]>. *

* *

- * Start and end values can be both numbers and ISO dates or datetimes. Dates - * and datetimes will be converted to their millisecond-precision timestamp. + * Start and end values can be both numbers and ISO dates or datetimes. Dates and datetimes will be converted to their + * millisecond-precision timestamp. *

* * Examples of valid interval maps are: @@ -75,14 +75,12 @@ * * *

- * All open intervals will be converted to closed intervals, as only - * closed intervals are supported. + * All open intervals will be converted to closed intervals, as only closed intervals are supported. *

* *

- * The most correct examples are those that include < > and proper commas - * and semicolons for separation, but the parser will be indulgent when - * possible. + * The most correct examples are those that include < > and proper commas and semicolons for separation, but the + * parser will be indulgent when possible. *

* * @author Eduardo Ramos @@ -93,14 +91,12 @@ public final class IntervalsParser { * Parses a {@link IntervalSet} type with one or more intervals. * * @param input Input string to parse - * @param timeZone Time zone to use or null to use default time zone (UTC) - * @return Resulting {@link IntervalSet}, or null if the input equals - * '<empty>' or is null - * @throws IllegalArgumentException Thrown if there are no intervals in the - * input string or bounds cannot be parsed into doubles or - * dates/datetimes. + * @param zoneId Time zone to use or null to use default time zone (UTC) + * @return Resulting {@link IntervalSet}, or null if the input equals '<empty>' or is null + * @throws IllegalArgumentException Thrown if there are no intervals in the input string or bounds cannot be parsed + * into doubles or dates/datetimes. */ - public static IntervalSet parseIntervalSet(String input, DateTimeZone timeZone) throws IllegalArgumentException { + public static IntervalSet parseIntervalSet(String input, ZoneId zoneId) throws IllegalArgumentException { if (input == null) { return null; } @@ -111,7 +107,7 @@ public static IntervalSet parseIntervalSet(String input, DateTimeZone timeZone) List> intervals; try { - intervals = parseIntervals(null, input, timeZone); + intervals = parseIntervals(null, input, zoneId); } catch (IOException ex) { throw new RuntimeException("Unexpected expection while parsing intervals", ex); } @@ -125,37 +121,30 @@ public static IntervalSet parseIntervalSet(String input, DateTimeZone timeZone) } /** - * Parses a {@link IntervalSet} type with one or more intervals. Default - * time zone is used (UTC). + * Parses a {@link IntervalSet} type with one or more intervals. Default time zone is used (UTC). * * @param input Input string to parse - * @return Resulting {@link IntervalSet}, or null if the input equals - * '<empty>' or is null - * @throws IllegalArgumentException Thrown if there are no intervals in the - * input string or bounds cannot be parsed into doubles or - * dates/datetimes. + * @return Resulting {@link IntervalSet}, or null if the input equals '<empty>' or is null + * @throws IllegalArgumentException Thrown if there are no intervals in the input string or bounds cannot be parsed + * into doubles or dates/datetimes. */ public static IntervalSet parseIntervalSet(String input) throws IllegalArgumentException { return parseIntervalSet(input, null); } /** - * Parses a {@link IntervalMap} type with one or more intervals, and their - * associated values. + * Parses a {@link IntervalMap} type with one or more intervals, and their associated values. * * @param Underlying type of the {@link IntervalMap} values - * @param typeClass Simple type or {@link IntervalMap} subtype for the - * result intervals' values. + * @param typeClass Simple type or {@link IntervalMap} subtype for the result intervals' values. * @param input Input string to parse - * @param timeZone Time zone to use or null to use default time zone (UTC) - * @return Resulting {@link IntervalMap}, or null if the input equals - * '<empty>' or is null - * @throws IllegalArgumentException Thrown if type class is not supported, - * any of the intervals don't have a value or have an invalid value, - * there are no intervals in the input string or bounds cannot be - * parsed into doubles or dates/datetimes. + * @param zoneId Time zone to use or null to use default time zone (UTC) + * @return Resulting {@link IntervalMap}, or null if the input equals '<empty>' or is null + * @throws IllegalArgumentException Thrown if type class is not supported, any of the intervals don't have a value + * or have an invalid value, there are no intervals in the input string or bounds cannot be parsed into + * doubles or dates/datetimes. */ - public static IntervalMap parseIntervalMap(Class typeClass, String input, DateTimeZone timeZone) throws IllegalArgumentException { + public static IntervalMap parseIntervalMap(Class typeClass, String input, ZoneId zoneId) throws IllegalArgumentException { if (typeClass == null) { throw new IllegalArgumentException("typeClass required"); } @@ -166,7 +155,7 @@ public static IntervalMap parseIntervalMap(Class typeClass, String inp List> intervals; try { - intervals = parseIntervals(typeClass, input, timeZone); + intervals = parseIntervals(typeClass, input, zoneId); } catch (IOException ex) { throw new RuntimeException("Unexpected expection while parsing intervals", ex); } @@ -208,36 +197,31 @@ public static IntervalMap parseIntervalMap(Class typeClass, String inp } /** - * Parses a {@link IntervalMap} type with one or more intervals, and their - * associated values. Default time zone is used (UTC). + * Parses a {@link IntervalMap} type with one or more intervals, and their associated values. Default time zone is + * used (UTC). * * @param Underlying type of the {@link IntervalMap} values - * @param typeClass Simple type or {@link IntervalMap} subtype for the - * result intervals' values. + * @param typeClass Simple type or {@link IntervalMap} subtype for the result intervals' values. * @param input Input string to parse - * @return Resulting {@link IntervalMap}, or null if the input equals - * '<empty>' or is null - * @throws IllegalArgumentException Thrown if type class is not supported, - * any of the intervals don't have a value or have an invalid value, - * there are no intervals in the input string or bounds cannot be - * parsed into doubles or dates/datetimes. + * @return Resulting {@link IntervalMap}, or null if the input equals '<empty>' or is null + * @throws IllegalArgumentException Thrown if type class is not supported, any of the intervals don't have a value + * or have an invalid value, there are no intervals in the input string or bounds cannot be parsed into + * doubles or dates/datetimes. */ public static IntervalMap parseIntervalMap(Class typeClass, String input) throws IllegalArgumentException { return parseIntervalMap(typeClass, input, null); } /** - * Parses intervals with values (of {@code typeClass} Class) or without - * values (null {@code typeClass} Class) + * Parses intervals with values (of {@code typeClass} Class) or without values (null {@code typeClass} Class) * * @param Type of the interval value - * @param typeClass Class of the intervals' values or null to parse - * intervals without values + * @param typeClass Class of the intervals' values or null to parse intervals without values * @param input Input to parse - * @param timeZone Time zone to use or null to use default time zone (UTC) + * @param zoneId Time zone to use or null to use default time zone (UTC) * @return List of Interval */ - private static List> parseIntervals(Class typeClass, String input, DateTimeZone timeZone) throws IOException, IllegalArgumentException { + private static List> parseIntervals(Class typeClass, String input, ZoneId zoneId) throws IOException, IllegalArgumentException { if (input == null) { return null; } @@ -265,7 +249,7 @@ private static List> parseIntervals(Class typeClass, switch (c) { case LEFT_BOUND_SQUARE_BRACKET: case LEFT_BOUND_BRACKET: - intervals.add(parseInterval(typeClass, reader, timeZone)); + intervals.add(parseInterval(typeClass, reader, zoneId)); break; default: // Ignore other chars outside of intervals @@ -279,7 +263,7 @@ private static List> parseIntervals(Class typeClass, return intervals; } - private static IntervalWithValue parseInterval(Class typeClass, StringReader reader, DateTimeZone timeZone) throws IOException { + private static IntervalWithValue parseInterval(Class typeClass, StringReader reader, ZoneId zoneId) throws IOException { ArrayList values = new ArrayList<>(); int r; @@ -289,7 +273,7 @@ private static IntervalWithValue parseInterval(Class typeClass, String switch (c) { case RIGHT_BOUND_SQUARE_BRACKET: case RIGHT_BOUND_BRACKET: - return buildInterval(typeClass, values, timeZone); + return buildInterval(typeClass, values, zoneId); case ' ': case '\t': case '\r': @@ -309,32 +293,35 @@ private static IntervalWithValue parseInterval(Class typeClass, String } } - return buildInterval(typeClass, values, timeZone); + return buildInterval(typeClass, values, zoneId); } - private static IntervalWithValue buildInterval(Class typeClass, ArrayList values, DateTimeZone timeZone) { + private static IntervalWithValue buildInterval(Class typeClass, ArrayList values, ZoneId zoneId) { if (typeClass == null && values.size() != 2) { throw new IllegalArgumentException("Each interval must have 2 values"); } else if (typeClass != null && values.size() != 3) { throw new IllegalArgumentException("Each interval must have 3 values"); } - double low = FormattingAndParsingUtils.parseDateTimeOrTimestamp(values.get(0), timeZone); - double high = FormattingAndParsingUtils.parseDateTimeOrTimestamp(values.get(1), timeZone); + try { + double low = FormattingAndParsingUtils.parseDateTimeOrTimestamp(values.get(0), zoneId); + double high = FormattingAndParsingUtils.parseDateTimeOrTimestamp(values.get(1), zoneId); - if (typeClass == null) { - return new IntervalWithValue(low, high, null); - } else { - String valString = values.get(2); - Object value = FormattingAndParsingUtils.convertValue(typeClass, valString); + if (typeClass == null) { + return new IntervalWithValue(low, high, null); + } else { + String valString = values.get(2); + Object value = FormattingAndParsingUtils.convertValue(typeClass, valString); - return new IntervalWithValue(low, high, value); + return new IntervalWithValue(low, high, value); + } + } catch (DateTimeParseException ex) { + throw new IllegalArgumentException("Invalid date/time/timestamp value", ex); } } /** - * Represents an Interval with an associated value for it. Only for internal - * usage in this class. + * Represents an Interval with an associated value for it. Only for internal usage in this class. * * @author Eduardo Ramos * @param Type of the value diff --git a/store/src/main/java/org/gephi/graph/impl/NodeImpl.java b/src/main/java/org/gephi/graph/impl/NodeImpl.java similarity index 86% rename from store/src/main/java/org/gephi/graph/impl/NodeImpl.java rename to src/main/java/org/gephi/graph/impl/NodeImpl.java index b3e0029f..07d02173 100644 --- a/store/src/main/java/org/gephi/graph/impl/NodeImpl.java +++ b/src/main/java/org/gephi/graph/impl/NodeImpl.java @@ -36,9 +36,8 @@ public class NodeImpl extends ElementImpl implements Node { public NodeImpl(Object id, GraphStore graphStore) { super(id, graphStore); checkIdType(id); - this.properties = GraphStoreConfiguration.ENABLE_NODE_PROPERTIES ? new NodePropertiesImpl() : null; - this.attributes = new Object[GraphStoreConfiguration.ELEMENT_ID_INDEX + 1]; - this.attributes[GraphStoreConfiguration.ELEMENT_ID_INDEX] = id; + this.properties = graphStore == null || graphStore.configuration.isEnableNodeProperties() + ? new NodePropertiesImpl() : null; } public NodeImpl(Object id) { @@ -78,6 +77,14 @@ ColumnStore getColumnStore() { return null; } + @Override + DefaultColumnsImpl.TableDefaultColumns getDefaultColumns() { + if (graphStore != null) { + return graphStore.defaultColumns.nodeDefaultColumns; + } + return null; + } + @Override public Table getTable() { if (graphStore != null) { @@ -164,6 +171,14 @@ public TextPropertiesImpl getTextProperties() { return properties.getTextProperties(); } + protected SpatialNodeDataImpl getSpatialData() { + return properties.getSpatialData(); + } + + protected void setSpatialData(SpatialNodeDataImpl spatialData) { + properties.setSpatialData(spatialData); + } + private void updateNodeInSpatialIndex() { if (storeId != NodeStore.NULL_ID && graphStore != null && graphStore.spatialIndex != null) { graphStore.spatialIndex.moveNode(this); @@ -186,30 +201,54 @@ protected void setNodeProperties(NodePropertiesImpl nodeProperties) { @Override public void setX(float x) { + if (Float.isNaN(x)) { + throw new IllegalArgumentException("x cannot be NaN"); + } properties.setX(x); updateNodeInSpatialIndex(); } @Override public void setY(float y) { + if (Float.isNaN(y)) { + throw new IllegalArgumentException("y cannot be NaN"); + } properties.setY(y); updateNodeInSpatialIndex(); } @Override public void setZ(float z) { + if (Float.isNaN(z)) { + throw new IllegalArgumentException("z cannot be NaN"); + } properties.setZ(z); updateNodeInSpatialIndex(); } @Override public void setPosition(float x, float y) { + if (Float.isNaN(x)) { + throw new IllegalArgumentException("x cannot be NaN"); + } + if (Float.isNaN(y)) { + throw new IllegalArgumentException("y cannot be NaN"); + } properties.setPosition(x, y); updateNodeInSpatialIndex(); } @Override public void setPosition(float x, float y, float z) { + if (Float.isNaN(x)) { + throw new IllegalArgumentException("x cannot be NaN"); + } + if (Float.isNaN(y)) { + throw new IllegalArgumentException("y cannot be NaN"); + } + if (Float.isNaN(z)) { + throw new IllegalArgumentException("z cannot be NaN"); + } properties.setPosition(x, y, z); updateNodeInSpatialIndex(); } @@ -241,6 +280,9 @@ public void setColor(Color color) { @Override public void setSize(float size) { + if (Float.isNaN(size)) { + throw new IllegalArgumentException("size cannot be NaN"); + } properties.setSize(size); updateNodeInSpatialIndex(); } @@ -273,6 +315,7 @@ protected static class NodePropertiesImpl implements NodeProperties { protected float size; protected boolean fixed; protected LayoutData layoutData; + protected SpatialNodeDataImpl spatialData; public NodePropertiesImpl() { this.textProperties = new TextPropertiesImpl(); @@ -419,6 +462,14 @@ public void setLayoutData(LayoutData layoutData) { this.layoutData = layoutData; } + public SpatialNodeDataImpl getSpatialData() { + return spatialData; + } + + public void setSpatialData(SpatialNodeDataImpl spatialData) { + this.spatialData = spatialData; + } + public int deepHashCode() { int hash = 3; hash = 53 * hash + Float.floatToIntBits(this.x); diff --git a/src/main/java/org/gephi/graph/impl/NodeIterableWrapper.java b/src/main/java/org/gephi/graph/impl/NodeIterableWrapper.java new file mode 100644 index 00000000..dab1e7b9 --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/NodeIterableWrapper.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import java.util.Iterator; +import java.util.Spliterator; +import java.util.function.Supplier; +import java.util.stream.StreamSupport; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.NodeIterable; + +public class NodeIterableWrapper extends ElementIterableWrapper implements NodeIterable { + + public NodeIterableWrapper(Supplier> iteratorSupplier, GraphLockImpl lock) { + super(iteratorSupplier, lock); + } + + public NodeIterableWrapper(Supplier> iteratorSupplier, Supplier> spliteratorSupplier, GraphLockImpl lock) { + super(iteratorSupplier, spliteratorSupplier, lock); + } + + @Override + public Node[] toArray() { + if (parallelPossible && lock != null) { + lock.readLock(); + try { + return StreamSupport.stream(spliterator(), true).toArray(Node[]::new); + } finally { + lock.readUnlock(); + } + } + return StreamSupport.stream(spliterator(), parallelPossible).toArray(Node[]::new); + } +} diff --git a/store/src/main/java/org/gephi/graph/impl/NodeStore.java b/src/main/java/org/gephi/graph/impl/NodeStore.java similarity index 73% rename from store/src/main/java/org/gephi/graph/impl/NodeStore.java rename to src/main/java/org/gephi/graph/impl/NodeStore.java index 8aae6768..d4f7dc00 100644 --- a/store/src/main/java/org/gephi/graph/impl/NodeStore.java +++ b/src/main/java/org/gephi/graph/impl/NodeStore.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.gephi.graph.impl; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; @@ -20,8 +21,15 @@ import it.unimi.dsi.fastutil.objects.ObjectSet; import java.util.ArrayList; import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.gephi.graph.api.Node; import org.gephi.graph.api.NodeIterable; @@ -31,21 +39,21 @@ public class NodeStore implements Collection, NodeIterable { protected final static int NULL_ID = -1; // Store protected final EdgeStore edgeStore; - protected final GraphStoreSpatialContextImpl spatialIndex; + protected final SpatialIndexImpl spatialIndex; // Locking (optional) - protected final GraphLock lock; + protected final GraphLockImpl lock; // Version protected final GraphVersion version; + // View store + protected final GraphViewStore viewStore; // Data protected int size; protected int garbageSize; protected int blocksCount; protected int currentBlockIndex; - protected NodeBlock blocks[]; + protected NodeBlock[] blocks; protected NodeBlock currentBlock; protected Object2IntOpenHashMap dictionary; - // View store - protected final GraphViewStore viewStore; public NodeStore() { initStore(); @@ -56,7 +64,7 @@ public NodeStore() { this.spatialIndex = null; } - public NodeStore(final EdgeStore edgeStore, final GraphStoreSpatialContextImpl spatialIndex, final GraphLock lock, final GraphViewStore viewStore, final GraphVersion graphVersion) { + public NodeStore(final EdgeStore edgeStore, final SpatialIndexImpl spatialIndex, final GraphLockImpl lock, final GraphViewStore viewStore, final GraphVersion graphVersion) { initStore(); this.lock = lock; this.edgeStore = edgeStore; @@ -122,6 +130,14 @@ public NodeImpl get(final int id) { return blocks[id / GraphStoreConfiguration.NODESTORE_BLOCK_SIZE].get(id); } + // Only used for Graph.getNodeByStoreId + public NodeImpl getForGetByStoreId(int id) { + if (id < 0 || !isValidIndex(id)) { + return null; + } + return blocks[id / GraphStoreConfiguration.NODESTORE_BLOCK_SIZE].get(id); + } + public NodeImpl get(final Object id) { int index = dictionary.getInt(id); if (index != NodeStore.NULL_ID) { @@ -163,6 +179,22 @@ public NodeStoreIterator iterator() { return new NodeStoreIterator(); } + @Override + public Spliterator spliterator() { + int end = blocksCount; + return new NodeSpliterator(0, end); + } + + @Override + public Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + @Override + public Stream parallelStream() { + return StreamSupport.stream(spliterator(), true); + } + @Override public NodeImpl[] toArray() { readLock(); @@ -231,6 +263,23 @@ public Collection toCollection() { return list; } + @Override + public Set toSet() { + readLock(); + + Set set = new HashSet<>(size); + + NodeStoreIterator itr = iterator(); + while (itr.hasNext()) { + NodeImpl n = itr.next(); + set.add(n); + } + + readUnlock(); + + return set; + } + @Override public boolean add(final Node n) { checkNonNullNodeObject(n); @@ -256,13 +305,10 @@ public boolean add(final Node n) { currentBlock.add(node); dictionary.put(node.getId(), node.storeId); } - if (viewStore != null) { - viewStore.addNode(node); - } node.indexAttributes(); - if (this.spatialIndex != null) { - this.spatialIndex.addNode(n); + if (spatialIndex != null) { + spatialIndex.addNode(node); } size++; @@ -288,11 +334,11 @@ public boolean remove(final Object o) { viewStore.removeNode(node); } - if (this.spatialIndex != null) { - this.spatialIndex.removeNode(node); + if (spatialIndex != null) { + spatialIndex.removeNode(node); } - node.clearAttributes(); + node.destroyAttributes(); incrementVersion(); @@ -358,7 +404,7 @@ public boolean containsAll(final Collection c) { } return found == c.size(); } - return false; + return true; } @Override @@ -423,8 +469,9 @@ public boolean retainAll(final Collection c) { } } return changed; - } else { + } else if (size > 0) { clear(); + return true; } return false; } @@ -637,7 +684,8 @@ public NodeImpl next() { public void remove() { checkWriteLock(); if (edgeStore != null) { - for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(pointer); edgeIterator.hasNext();) { + for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(pointer, false); edgeIterator + .hasNext();) { edgeIterator.next(); edgeIterator.remove(); } @@ -645,4 +693,141 @@ public void remove() { NodeStore.this.remove(pointer); } } + + private final class NodeSpliterator implements Spliterator { + + private final int endBlockExclusive; + private int blockIndex; + private int indexInBlock; + private NodeImpl[] currentArray; + private int currentLength; + private final int expectedVersion; + private int totalSize; + private int consumed; + + NodeSpliterator(int startBlock, int endBlockExclusive) { + this.blockIndex = startBlock; + this.endBlockExclusive = endBlockExclusive; + this.expectedVersion = version != null ? version.getNodeVersion() : 0; + this.consumed = 0; + + // Use the total store size for the root spliterator (covering all blocks) + if (startBlock == 0 && endBlockExclusive == blocksCount) { + this.totalSize = NodeStore.this.size(); + } else { + // For split spliterators, compute proportionally + this.totalSize = computeExactSize(startBlock, endBlockExclusive); + } + + if (startBlock < endBlockExclusive) { + NodeBlock b = blocks[startBlock]; + currentArray = b.backingArray; + currentLength = b.nodeLength; + indexInBlock = 0; + } else { + currentArray = null; + currentLength = 0; + indexInBlock = 0; + } + } + + private int computeExactSize(int start, int end) { + int sum = 0; + for (int i = start; i < end; i++) { + NodeBlock b = blocks[i]; + if (b != null) { + // Exact count: nodeLength minus garbageLength + sum += (b.nodeLength - b.garbageLength); + } + } + return sum; + } + + private void advanceBlock() { + blockIndex++; + if (blockIndex < endBlockExclusive) { + NodeBlock b = blocks[blockIndex]; + currentArray = b.backingArray; + currentLength = b.nodeLength; + indexInBlock = 0; + } else { + currentArray = null; + currentLength = 0; + indexInBlock = 0; + } + } + + private void checkForComodification() { + if (version != null && expectedVersion != version.getNodeVersion()) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean tryAdvance(Consumer action) { + checkForComodification(); + while (currentArray != null) { + while (indexInBlock < currentLength) { + NodeImpl n = currentArray[indexInBlock++]; + if (n != null) { + consumed++; + action.accept(n); + return true; + } + } + advanceBlock(); + } + return false; + } + + @Override + public Spliterator trySplit() { + // Only split at block boundaries to preserve encounter order + if (indexInBlock != 0) { + return null; + } + + int currentPos = blockIndex; + int remainingBlocks = endBlockExclusive - currentPos; + + if (remainingBlocks <= 1) { + return null; + } + + int mid = currentPos + remainingBlocks / 2; + + // Create left half + NodeSpliterator left = new NodeSpliterator(currentPos, mid); + + // Update this spliterator to become the right half + blockIndex = mid; + if (mid < endBlockExclusive) { + NodeBlock b = blocks[mid]; + currentArray = b.backingArray; + currentLength = b.nodeLength; + indexInBlock = 0; + } else { + currentArray = null; + currentLength = 0; + indexInBlock = 0; + } + + // Update this spliterator size + this.totalSize = totalSize - left.totalSize; + + return left; + } + + @Override + public long estimateSize() { + // Use the exact totalSize minus what we've consumed + long remaining = totalSize - consumed; + return remaining < 0 ? 0 : remaining; + } + + @Override + public int characteristics() { + return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SIZED | Spliterator.SUBSIZED; + } + } } diff --git a/src/main/java/org/gephi/graph/impl/NodesQuadTree.java b/src/main/java/org/gephi/graph/impl/NodesQuadTree.java new file mode 100644 index 00000000..82b152f9 --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/NodesQuadTree.java @@ -0,0 +1,1725 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.ConcurrentModificationException; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.EdgeIterable; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.NodeIterable; +import org.gephi.graph.api.Rect2D; + +/** + * Adapted from https://bitbucket.org/C3/quadtree/wiki/Home + * + * @author Eduardo Ramos + */ +public class NodesQuadTree { + + protected final GraphLockImpl lock = new GraphLockImpl(); + + private final QuadTreeNode quadTreeRoot; + private final int maxLevels; + private final int maxObjectsPerNode; + private final GraphStore graphStore; + private int version = 0; + + public NodesQuadTree(Rect2D rect) { + this(null, rect); + } + + public NodesQuadTree(GraphStore store, Rect2D rect) { + this(store, rect, GraphStoreConfiguration.SPATIAL_INDEX_MAX_LEVELS, + GraphStoreConfiguration.SPATIAL_INDEX_MAX_OBJECTS_PER_NODE); + } + + public NodesQuadTree(GraphStore store, Rect2D rect, int maxLevels, int maxObjectsPerNode) { + this.quadTreeRoot = new QuadTreeNode(rect); + this.maxLevels = maxLevels; + this.maxObjectsPerNode = maxObjectsPerNode; + this.graphStore = store; + } + + public Rect2D quadRect() { + return quadTreeRoot.quadRect(); + } + + public NodeIterable getNodes(Rect2D searchRect) { + return quadTreeRoot.getNodes(searchRect); + } + + public NodeIterable getNodes(Rect2D searchRect, boolean approximate) { + return quadTreeRoot.getNodes(searchRect, approximate); + } + + public NodeIterable getNodes(Rect2D searchRect, boolean approximate, Predicate predicate) { + return quadTreeRoot.getNodes(searchRect, approximate, predicate); + } + + public NodeIterable getAllNodes() { + return quadTreeRoot.getAllNodes(); + } + + public NodeIterable getAllNodes(Predicate predicate) { + return quadTreeRoot.getAllNodes(predicate); + } + + public EdgeIterable getEdges() { + return quadTreeRoot.getAllEdges(); + } + + public EdgeIterable getEdges(Rect2D searchRect) { + return quadTreeRoot.getEdges(searchRect); + } + + public EdgeIterable getEdges(Rect2D searchRect, boolean approximate) { + return quadTreeRoot.getEdges(searchRect, approximate); + } + + public EdgeIterable getEdges(Rect2D searchRect, boolean approximate, Predicate predicate) { + return quadTreeRoot.getEdges(searchRect, approximate, predicate); + } + + public void incrementVersion() { + version++; + } + + public boolean updateNode(NodeImpl item, float minX, float minY, float maxX, float maxY) { + writeLock(); + try { + final SpatialNodeDataImpl obj = item.getSpatialData(); + if (obj != null) { + obj.updateBoundaries(minX, minY, maxX, maxY); + quadTreeRoot.update(item); + version++; + return true; + } else { + return false; + } + } finally { + writeUnlock(); + } + } + + public boolean addNode(NodeImpl item) { + writeLock(); + try { + final float x = item.x(); + final float y = item.y(); + final float size = item.size(); + + final float minX = x - size; + final float minY = y - size; + final float maxX = x + size; + final float maxY = y + size; + + SpatialNodeDataImpl spatialData = item.getSpatialData(); + if (spatialData == null) { + spatialData = new SpatialNodeDataImpl(minX, minY, maxX, maxY); + item.setSpatialData(spatialData); + quadTreeRoot.insert(item); + version++; + return true; + } else { + return false; + } + } finally { + writeUnlock(); + } + } + + public void clear() { + writeLock(); + try { + for (Node node : getAllNodes()) { + SpatialNodeDataImpl spatialData = ((NodeImpl) node).getSpatialData(); + spatialData.clear(); + } + quadTreeRoot.clear(); + version++; + } finally { + writeUnlock(); + } + } + + public boolean removeNode(NodeImpl item) { + writeLock(); + try { + final SpatialNodeDataImpl spatialData = item.getSpatialData(); + if (spatialData != null && spatialData.quadTreeNode != null) { + quadTreeRoot.delete(item, true); + version++; + return true; + } + return false; + } finally { + writeUnlock(); + } + } + + public int getObjectCount() { + readLock(); + int count = quadTreeRoot.objectCount(); + readUnlock(); + return count; + } + + public void readLock() { + if (lock != null) { + lock.readLock(); + } + } + + public void readUnlock() { + if (lock != null) { + lock.readUnlock(); + } + } + + public void writeLock() { + if (lock != null) { + lock.writeLock(); + } + } + + public void writeUnlock() { + if (lock != null) { + lock.writeUnlock(); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + quadTreeRoot.toString(sb); + + return sb.toString(); + } + + public int getDepth() { + readLock(); + int depth = quadTreeRoot.getDepth(); + readUnlock(); + return depth; + } + + public int getNodeCount(boolean keepOnlyWithObjects) { + readLock(); + int count = quadTreeRoot.getNodeCount(keepOnlyWithObjects); + readUnlock(); + return count; + } + + public Rect2D getBoundaries() { + return getBoundaries(null); + } + + public Rect2D getBoundaries(Predicate predicate) { + readLock(); + try { + NodeIterable allNodes = predicate == null ? getAllNodes() : getAllNodes(predicate); + + float minX = Float.POSITIVE_INFINITY; + float minY = Float.POSITIVE_INFINITY; + float maxX = Float.NEGATIVE_INFINITY; + float maxY = Float.NEGATIVE_INFINITY; + + boolean hasNodes = false; + + for (Node node : allNodes) { + if (node == null) { + continue; + } + SpatialNodeDataImpl spatialData = ((NodeImpl) node).getSpatialData(); + if (spatialData != null) { + hasNodes = true; + if (spatialData.minX < minX) { + minX = spatialData.minX; + } + if (spatialData.minY < minY) { + minY = spatialData.minY; + } + if (spatialData.maxX > maxX) { + maxX = spatialData.maxX; + } + if (spatialData.maxY > maxY) { + maxY = spatialData.maxY; + } + } + } + + return hasNodes ? new Rect2D(minX, minY, maxX, maxY) : new Rect2D(Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); + } finally { + readUnlock(); + } + } + + private int collectOverlapping(QuadTreeNode node, Rect2D searchRect, Set resultSet) { + if (searchRect != null && !node.rect.intersects(searchRect)) { + return 0; + } + + // If this node has objects and intersects with search rect, add it + int nodeCount = 0; + if (node.objectCount > 0) { + resultSet.add(node); + nodeCount += node.objectCount; + } + + // Recursively check children + if (node.childTL != null) { + nodeCount += collectOverlapping(node.childTL, searchRect, resultSet); + nodeCount += collectOverlapping(node.childTR, searchRect, resultSet); + nodeCount += collectOverlapping(node.childBL, searchRect, resultSet); + nodeCount += collectOverlapping(node.childBR, searchRect, resultSet); + } + return nodeCount; + } + + protected class QuadTreeNode { + + private NodeImpl[] objects = null; // Fixed-size array for objects + private int objectCount = 0; // Number of objects currently in this node + private final Rect2D rect; // The area this QuadTree represents + + private final QuadTreeNode parent; // The parent of this quad + private final int level; + private int size = 0; // Total number of objects in this node and its children + + private QuadTreeNode childTL = null; // Top Left Child + private QuadTreeNode childTR = null; // Top Right Child + private QuadTreeNode childBL = null; // Bottom Left Child + private QuadTreeNode childBR = null; // Bottom Right Child + + public Rect2D quadRect() { + return rect; + } + + public QuadTreeNode topLeftChild() { + return childTL; + } + + public QuadTreeNode topRightChild() { + return childTR; + } + + public QuadTreeNode bottomLeftChild() { + return childBL; + } + + public QuadTreeNode bottomRightChild() { + return childBR; + } + + public QuadTreeNode parent() { + return parent; + } + + public int count() { + return size; + } + + public boolean isEmptyLeaf() { + return size == 0 && childTL == null; + } + + public QuadTreeNode(Rect2D rect) { + this(null, 0, rect); + } + + private QuadTreeNode(QuadTreeNode parent, int level, Rect2D rect) { + this.level = level; + this.rect = rect; + this.parent = parent; + } + + private void add(NodeImpl item) { + if (objects == null) { + // Allocate initial array + objects = new NodeImpl[maxObjectsPerNode / 16]; + } + + // Check if item is already in this node (avoid duplicates) + SpatialNodeDataImpl spatialData = item.getSpatialData(); + if (spatialData.quadTreeNode == this && spatialData.arrayIndex >= 0) { + return; // Already in this node + } + + // Resize array if needed (can happen when at max depth or when objects don't + // fit in children) + if (objectCount >= objects.length) { + NodeImpl[] newArray = new NodeImpl[objects.length * 2]; + System.arraycopy(objects, 0, newArray, 0, objects.length); + objects = newArray; + } + + // Add to array + objects[objectCount] = item; + spatialData.setQuadTreeNode(this); + spatialData.setArrayIndex(objectCount); + objectCount++; + + // Update size and edge size for this node and all parents + QuadTreeNode node = this; + while (node != null) { + node.size++; + node = node.parent; + } + } + + private void remove(NodeImpl item) { + if (objects != null && objectCount > 0) { + SpatialNodeDataImpl spatialData = item.getSpatialData(); + int index = spatialData.arrayIndex; + + if (index >= 0 && index < objectCount && objects[index] == item) { + // Swap with last element for O(1) removal + objectCount--; + NodeImpl lastItem = objects[objectCount]; + objects[index] = lastItem; + objects[objectCount] = null; + + // Update the moved item's index + if (lastItem != null && index < objectCount) { + lastItem.getSpatialData().setArrayIndex(index); + } + + // Clear removed item's data + spatialData.clear(); + + // Update size + QuadTreeNode node = this; + while (node != null) { + node.size--; + node = node.parent; + } + } + } + } + + private int objectCount() { + return size; + } + + private void subdivide() { + // We've reached capacity, subdivide... + final float minX = rect.minX; + final float halfX = (rect.minX + rect.maxX) / 2; + final float maxX = rect.maxX; + + final float minY = rect.minY; + final float halfY = (rect.minY + rect.maxY) / 2; + final float maxY = rect.maxY; + + childTL = new QuadTreeNode(this, level + 1, new Rect2D(minX, minY, halfX, halfY)); + childTR = new QuadTreeNode(this, level + 1, new Rect2D(halfX, minY, maxX, halfY)); + childBL = new QuadTreeNode(this, level + 1, new Rect2D(minX, halfY, halfX, maxY)); + childBR = new QuadTreeNode(this, level + 1, new Rect2D(halfX, halfY, maxX, maxY)); + + // Keep track of objects that couldn't be moved + NodeImpl[] remainingObjects = new NodeImpl[objectCount]; + int remainingCount = 0; + + // If they're completely contained by the quad, bump objects down + for (int i = 0; i < objectCount; i++) { + NodeImpl obj = objects[i]; + QuadTreeNode destTree = getDestinationTree(obj); + if (destTree != this) { + // Insert to the appropriate tree + destTree.insert(obj); + + // Update size + QuadTreeNode node = this; + while (node != null) { + node.size--; + node = node.parent; + } + } else { + // Keep this object in the current node + remainingObjects[remainingCount] = obj; + obj.getSpatialData().setArrayIndex(remainingCount); + remainingCount++; + } + } + + // Update this node's object array + for (int i = 0; i < remainingCount; i++) { + objects[i] = remainingObjects[i]; + } + for (int i = remainingCount; i < objectCount; i++) { + objects[i] = null; + } + objectCount = remainingCount; + } + + private QuadTreeNode getDestinationTree(NodeImpl item) { + // If a child can't contain an object, it will live in this Quad + final QuadTreeNode destTree; + + SpatialNodeDataImpl spatialData = item.getSpatialData(); + final float minX = spatialData.minX; + final float minY = spatialData.minY; + final float maxX = spatialData.maxX; + final float maxY = spatialData.maxY; + + if (childTL.quadRect().contains(minX, minY, maxX, maxY)) { + destTree = childTL; + } else if (childTR.quadRect().contains(minX, minY, maxX, maxY)) { + destTree = childTR; + } else if (childBL.quadRect().contains(minX, minY, maxX, maxY)) { + destTree = childBL; + } else if (childBR.quadRect().contains(minX, minY, maxX, maxY)) { + destTree = childBR; + } else { + destTree = this; + } + + return destTree; + } + + private void relocate(NodeImpl item) { + SpatialNodeDataImpl spatialData = item.getSpatialData(); + + // Are we still inside our parent? + if (quadRect().contains(spatialData.minX, spatialData.minY, spatialData.maxX, spatialData.maxY)) { + // Good, have we moved inside any of our children? + if (childTL != null) { + QuadTreeNode dest = getDestinationTree(item); + if (spatialData.quadTreeNode != dest) { + // Delete the item from this quad and add it to our + // child + // Note: Do NOT clean during this call, it can + // potentially delete our destination quad + QuadTreeNode formerOwner = spatialData.quadTreeNode; + delete(item, false); + dest.insert(item); + + // Clean up ourselves + formerOwner.cleanUpwards(); + } + } + } else { + // We don't fit here anymore, move up, if we can + if (parent != null) { + parent.relocate(item); + } + } + } + + private void cleanUpwards() { + if (childTL != null) { + // If all the children are empty leaves, delete all the children + if (childTL.isEmptyLeaf() && childTR.isEmptyLeaf() && childBL.isEmptyLeaf() && childBR.isEmptyLeaf()) { + childTL = null; + childTR = null; + childBL = null; + childBR = null; + + if (parent != null && count() == 0) { + parent.cleanUpwards(); + } + } + } else { + // I could be one of 4 empty leaves, tell my parent to clean up + if (parent != null && count() == 0) { + parent.cleanUpwards(); + } + } + } + + private void clear() { + // clear out the children, if we have any + if (childTL != null) { + childTL.clear(); + childTR.clear(); + childBL.clear(); + childBR.clear(); + } + + // clear any objects at this level + if (objects != null) { + // Clear spatial data references for all objects + for (int i = 0; i < objectCount; i++) { + if (objects[i] != null) { + SpatialNodeDataImpl spatialData = objects[i].getSpatialData(); + spatialData.clear(); + objects[i] = null; + } + } + objects = null; + objectCount = 0; + } + + // Reset size and edge size + size = 0; + + // Set the children to null + childTL = null; + childTR = null; + childBL = null; + childBR = null; + } + + private void delete(NodeImpl node, boolean clean) { + SpatialNodeDataImpl spatialData = node.getSpatialData(); + if (spatialData.quadTreeNode != null) { + if (spatialData.quadTreeNode == this) { + remove(node); + if (clean) { + cleanUpwards(); + } + } else { + spatialData.quadTreeNode.delete(node, clean); + } + } + } + + private void insert(NodeImpl item) { + SpatialNodeDataImpl spatialData = item.getSpatialData(); + // If this quad doesn't contain the items rectangle, do nothing, + // unless we are the root + if (!rect.contains(spatialData.minX, spatialData.minY, spatialData.maxX, spatialData.maxY)) { + if (parent == null) { + // This object is outside of the QuadTreeXNA bounds, we + // should add it at the root level + add(item); + } else { + throw new IllegalStateException( + "We are not the root, and this object doesn't fit here. How did we get here?"); + } + } + + if (objects == null || (childTL == null && (level >= maxLevels || objectCount + 1 <= maxObjectsPerNode))) { + // If there's room to add the object, just add it + add(item); + } else { + // No quads, create them and bump objects down where appropriate + if (childTL == null) { + subdivide(); + } + + // Find out which tree this object should go in and add it there + final QuadTreeNode destTree = getDestinationTree(item); + if (destTree == this) { + add(item); + } else { + destTree.insert(item); + } + } + } + + private NodeIterable getNodes(Rect2D searchRect) { + return new QuadTreeNodesIterable(searchRect); + } + + private NodeIterable getNodes(Rect2D searchRect, boolean approximate) { + return new QuadTreeNodesIterable(searchRect, approximate); + } + + private NodeIterable getNodes(Rect2D searchRect, boolean approximate, Predicate predicate) { + return new FilteredQuadTreeNodeIterable(searchRect, approximate, predicate); + } + + private NodeIterable getAllNodes() { + return new QuadTreeNodesIterable(null); + } + + private NodeIterable getAllNodes(Predicate predicate) { + return new FilteredQuadTreeNodeIterable(null, false, predicate); + } + + private EdgeIterable getEdges(Rect2D searchRect) { + return new QuadTreeEdgesIterable(searchRect); + } + + private EdgeIterable getEdges(Rect2D searchRect, boolean approximate) { + return new QuadTreeEdgesIterable(searchRect, approximate); + } + + private EdgeIterable getEdges(Rect2D searchRect, boolean approximate, Predicate predicate) { + return new FilteredQuadTreeEdgeIterable(searchRect, approximate, predicate); + } + + private EdgeIterable getAllEdges() { + return new QuadTreeEdgesIterable(null); + } + + private void update(NodeImpl item) { + SpatialNodeDataImpl spatialData = item.getSpatialData(); + if (spatialData.quadTreeNode != null) { + spatialData.quadTreeNode.relocate(item); + } else { + relocate(item); + } + } + + private int getDepth() { + int maxLevel = level; + if (childTL != null) { + maxLevel = Math.max(maxLevel, childTL.getDepth()); + maxLevel = Math.max(maxLevel, childBR.getDepth()); + maxLevel = Math.max(maxLevel, childTR.getDepth()); + maxLevel = Math.max(maxLevel, childBL.getDepth()); + } + return maxLevel; + } + + private int getNodeCount(boolean withObjects) { + int count = 1; // Count this node + + // If withObjects is true, only count nodes that have objects + if (withObjects && (objects == null || objectCount == 0)) { + count = 0; + } + + // Recursively count children + if (childTL != null) { + count += childTL.getNodeCount(withObjects); + count += childTR.getNodeCount(withObjects); + count += childBL.getNodeCount(withObjects); + count += childBR.getNodeCount(withObjects); + } + + return count; + } + + public void toString(StringBuilder sb) { + for (int i = 0; i < level; i++) { + sb.append(" "); + } + sb.append(rect.toString()).append('\n'); + + if (objects != null) { + for (int j = 0; j <= level; j++) { + sb.append(" "); + } + + sb.append(objectCount).append(" objects \n"); + } + + if (childTL != null) { + childTL.toString(sb); + childTR.toString(sb); + childBL.toString(sb); + childBR.toString(sb); + } + } + } + + private class FilteredQuadTreeNodeIterable extends QuadTreeNodesIterable { + + private final Predicate predicate; + + public FilteredQuadTreeNodeIterable(Rect2D searchRect, boolean approximate, Predicate predicate) { + super(searchRect, approximate); + this.predicate = predicate; + } + + @Override + public Iterator iterator() { + return new QuadTreeNodesIterator(quadTreeRoot, searchRect, approximate, predicate); + } + + @Override + public Spliterator spliterator() { + return new FilteredQuadTreeNodesSpliterator(quadTreeRoot, searchRect, approximate, predicate); + } + } + + private class QuadTreeNodesIterable implements NodeIterable { + + protected final Rect2D searchRect; + protected final boolean approximate; + + public QuadTreeNodesIterable(Rect2D searchRect) { + this(searchRect, GraphStoreConfiguration.SPATIAL_INDEX_APPROXIMATE_AREA_SEARCH); + } + + public QuadTreeNodesIterable(Rect2D searchRect, boolean approximate) { + this.searchRect = searchRect; + this.approximate = approximate; + } + + @Override + public Iterator iterator() { + return new QuadTreeNodesIterator(quadTreeRoot, searchRect, approximate); + } + + @Override + public Spliterator spliterator() { + return new QuadTreeNodesSpliterator(quadTreeRoot, searchRect, approximate); + } + + @Override + public Node[] toArray() { + return toCollection().toArray(new Node[0]); + } + + @Override + public Collection toCollection() { + final List list = new ArrayList<>(); + + for (Node node : this) { + list.add(node); + } + + return list; + } + + @Override + public Set toSet() { + final Set set = new HashSet<>(); + + for (Node node : this) { + set.add(node); + } + + return set; + } + + @Override + public void doBreak() { + readUnlock(); + } + } + + private class FilteredQuadTreeEdgeIterable extends QuadTreeEdgesIterable { + + private final Predicate predicate; + + public FilteredQuadTreeEdgeIterable(Rect2D searchRect, boolean approximate, Predicate predicate) { + super(searchRect, approximate); + this.predicate = predicate; + } + + @Override + public Iterator iterator() { + return new QuadTreeEdgesIterator(quadTreeRoot, searchRect, approximate, predicate); + } + + @Override + public Spliterator spliterator() { + HashSet overlappingNodes = new HashSet<>(); + int nodeCount = collectOverlapping(quadTreeRoot, searchRect, overlappingNodes); + if (useDirectIterator(nodeCount)) { + return new QuadTreeGlobalEdgesSpliterator(searchRect, approximate, overlappingNodes, predicate); + } + // Use local iterator + return new FilteredQuadTreeEdgesSpliterator(quadTreeRoot, searchRect, approximate, predicate); + } + } + + private class QuadTreeEdgesIterable implements EdgeIterable { + + protected final Rect2D searchRect; + protected final boolean approximate; + + public QuadTreeEdgesIterable(Rect2D searchRect) { + this(searchRect, GraphStoreConfiguration.SPATIAL_INDEX_APPROXIMATE_AREA_SEARCH); + } + + public QuadTreeEdgesIterable(Rect2D searchRect, boolean approximate) { + this.searchRect = searchRect; + this.approximate = approximate; + } + + @Override + public Iterator iterator() { + return new QuadTreeEdgesIterator(quadTreeRoot, searchRect, approximate); + } + + protected boolean useDirectIterator(int nodeCount) { + return (float) nodeCount / quadTreeRoot.size > GraphStoreConfiguration.SPATIAL_INDEX_LOCAL_ITERATOR_THRESHOLD; + } + + @Override + public Spliterator spliterator() { + if (searchRect == null) { + // Special case: all edges + return new QuadTreeGlobalEdgesSpliterator(null, approximate, null, null); + } + HashSet overlappingNodes = new HashSet<>(); + int nodeCount = collectOverlapping(quadTreeRoot, searchRect, overlappingNodes); + if (approximate && nodeCount == quadTreeRoot.size) { + // Optimisation: approximate search and all nodes overlapping, so just return + // all edges + return new QuadTreeGlobalEdgesSpliterator(null, true, null, null); + } else if (useDirectIterator(nodeCount)) { + return new QuadTreeGlobalEdgesSpliterator(searchRect, approximate, overlappingNodes, null); + } + // Use local iterator + return new QuadTreeEdgesSpliterator(quadTreeRoot, searchRect, approximate); + } + + @Override + public Edge[] toArray() { + return toCollection().toArray(new Edge[0]); + } + + @Override + public Collection toCollection() { + final List list = new ArrayList<>(); + + for (Edge edge : this) { + list.add(edge); + } + + return list; + } + + @Override + public Set toSet() { + final Set set = new HashSet<>(); + + for (Edge edge : this) { + set.add(edge); + } + + return set; + } + + @Override + public void doBreak() { + readUnlock(); + } + + } + + private class QuadTreeEdgesIterator implements Iterator { + + private final EdgeStore.EdgeInOutMultiIterator edgeIterator; + private final Predicate predicate; + private boolean finished = false; + private Edge next; + + public QuadTreeEdgesIterator(QuadTreeNode root, Rect2D searchRect, boolean approximate) { + this(root, searchRect, approximate, null); + } + + public QuadTreeEdgesIterator(QuadTreeNode root, Rect2D searchRect, boolean approximate, Predicate predicate) { + this.predicate = predicate; + readLock(); + + // Create a node iterator for the quad tree + final QuadTreeNodesIterator nodeIterator = new QuadTreeNodesIterator(root, searchRect, approximate); + + // Create the edge iterator using the EdgeStore method + this.edgeIterator = graphStore.edgeStore.edgeIterator(new Iterator<>() { + @Override + public boolean hasNext() { + return nodeIterator.hasNext(); + } + + @Override + public NodeImpl next() { + return nodeIterator.next(); + } + }, true); + } + + @Override + public boolean hasNext() { + if (finished) { + return false; + } + + if (next != null) { + return true; + } + + // Look for next edge that passes predicate + while (edgeIterator != null && edgeIterator.hasNext()) { + Edge edge = edgeIterator.next(); + if (predicate == null || predicate.test(edge)) { + next = edge; + return true; + } + } + + readUnlock(); + finished = true; + return false; + } + + @Override + public Edge next() { + if (next == null && !hasNext()) { + throw new IllegalStateException("No next available!"); + } + + Edge result = next; + next = null; + return result; + } + } + + private class QuadTreeNodesIterator implements Iterator { + + private final Rect2D searchRect; + private final boolean approximate; + private final Predicate predicate; + private final Deque nodesStack = new ArrayDeque<>(); + private final Deque fullyContainedStack = new ArrayDeque<>(); + + // Current: + private Iterator currentIterator; + private boolean currentFullyContained; + private boolean finished = false; + + private NodeImpl next; + + public QuadTreeNodesIterator(QuadTreeNode root, Rect2D searchRect, boolean approximate) { + this(root, searchRect, approximate, null); + } + + public QuadTreeNodesIterator(QuadTreeNode root, Rect2D searchRect, boolean approximate, Predicate predicate) { + this.searchRect = searchRect; + this.approximate = approximate; + this.predicate = predicate; + + readLock(); + + // Null rect means get all + currentFullyContained = searchRect == null; + + // We always add the root and don't test for the root being fully + // contained, to correctly handle the case of nodes out of the quad + // tree bounds + addChildrenToVisit(root, currentFullyContained); + currentIterator = root.objects != null ? new ArrayIterator(root.objects, root.objectCount) : null; + } + + private void addChildrenToVisit(QuadTreeNode quadTreeNode, boolean fullyContained) { + if (quadTreeNode.childTL != null) { + nodesStack.push(quadTreeNode.childBR); + nodesStack.push(quadTreeNode.childBL); + nodesStack.push(quadTreeNode.childTR); + nodesStack.push(quadTreeNode.childTL); + + fullyContainedStack.push(fullyContained); + fullyContainedStack.push(fullyContained); + fullyContainedStack.push(fullyContained); + fullyContainedStack.push(fullyContained); + } + } + + @Override + public boolean hasNext() { + if (finished) { + return false; + } + + if (next != null) { + return true; + } + + while (currentIterator != null || !nodesStack.isEmpty()) { + if (currentIterator != null) { + while (currentIterator.hasNext()) { + final NodeImpl elem = currentIterator.next(); + + // First check spatial conditions + boolean spatialMatch; + if (approximate || currentFullyContained) { + // In approximate mode or when fully contained, include all objects + spatialMatch = true; + } else { + // In exact mode, check intersection + final SpatialNodeDataImpl spatialData = elem.getSpatialData(); + spatialMatch = searchRect + .intersects(spatialData.minX, spatialData.minY, spatialData.maxX, spatialData.maxY); + } + + // If spatial conditions are met, check predicate + if (spatialMatch && (predicate == null || predicate.test(elem))) { + next = elem; + return true; + } + } + + currentIterator = null; + } else { + final QuadTreeNode pointer = nodesStack.pop(); + + currentFullyContained = fullyContainedStack.pop() || searchRect.contains(pointer.rect); + + if (currentFullyContained || pointer.rect.intersects(searchRect)) { + addChildrenToVisit(pointer, currentFullyContained); + currentIterator = pointer.objects != null + ? new ArrayIterator(pointer.objects, pointer.objectCount) : null; + } else { + currentIterator = null; + } + } + } + + readUnlock(); + finished = true; + return false; + } + + @Override + public NodeImpl next() { + if (next == null) { + throw new IllegalStateException("No next available!"); + } + + final NodeImpl node = next; + + next = null; + return node; + } + + } + + // Helper class to iterate over array elements + private static class ArrayIterator implements Iterator { + private final NodeImpl[] array; + private final int size; + private final Predicate predicate; + private int index = 0; + private NodeImpl next; + + public ArrayIterator(NodeImpl[] array, int size) { + this(array, size, null); + } + + public ArrayIterator(NodeImpl[] array, int size, Predicate predicate) { + this.array = array; + this.size = size; + this.predicate = predicate; + } + + @Override + public boolean hasNext() { + if (next != null) { + return true; + } + + // Find next element that passes predicate + while (index < size) { + NodeImpl candidate = array[index++]; + if (predicate == null || predicate.test(candidate)) { + next = candidate; + return true; + } + } + return false; + } + + @Override + public NodeImpl next() { + if (next == null && !hasNext()) { + throw new IllegalStateException("No more elements"); + } + NodeImpl result = next; + next = null; + return result; + } + } + + private abstract class AbstractQuadTreeSpliterator implements Spliterator { + protected final Rect2D searchRect; + protected final boolean approximate; + protected final Deque nodesStack = new ArrayDeque<>(); + protected final Deque fullyContainedStack = new ArrayDeque<>(); + + protected final int expectedVersion; + protected Iterator currentIterator; + protected boolean currentFullyContained; + protected T next; + protected int remainingSize; + + protected AbstractQuadTreeSpliterator(QuadTreeNode root, Rect2D searchRect, boolean approximate) { + this.searchRect = searchRect; + this.approximate = approximate; + this.expectedVersion = version; + + // Null rect means get all + currentFullyContained = searchRect == null; + + // Initialize with root + addNode(root, currentFullyContained); + currentIterator = createIteratorForNode(root); + + // For SIZED characteristic, we need exact count + if (searchRect == null) { + // Getting all elements, so we can use the maintained size + remainingSize = root.size; + } else if (approximate) { + // In approximate mode, count all elements in intersecting quadrants + remainingSize = countNodesInRectApproximate(root, searchRect); + } else { + // Need to count elements in the search rect + remainingSize = countNodesInRect(root, searchRect); + } + } + + protected AbstractQuadTreeSpliterator(QuadTreeNode node, Rect2D searchRect, boolean approximate, int expectedVersion, boolean fullyContained, int size) { + this.searchRect = searchRect; + this.approximate = approximate; + this.expectedVersion = expectedVersion; + this.remainingSize = size; + this.currentFullyContained = fullyContained; + + if (node != null) { + addNode(node, fullyContained); + currentIterator = createIteratorForNode(node); + } else { + currentIterator = null; + } + } + + protected void checkForComodification() { + if (version != expectedVersion) { + throw new ConcurrentModificationException(); + } + } + + protected void addNode(QuadTreeNode node, boolean fullyContained) { + if (node.childTL != null) { + nodesStack.push(node.childBR); + nodesStack.push(node.childBL); + nodesStack.push(node.childTR); + nodesStack.push(node.childTL); + + fullyContainedStack.push(fullyContained); + fullyContainedStack.push(fullyContained); + fullyContainedStack.push(fullyContained); + fullyContainedStack.push(fullyContained); + } + } + + protected int countNodesInRect(QuadTreeNode node, Rect2D rect) { + int count = 0; + + // Count objects at this level + if (node.objects != null) { + for (int i = 0; i < node.objectCount; i++) { + NodeImpl obj = node.objects[i]; + SpatialNodeDataImpl spatialData = obj.getSpatialData(); + if (rect.intersects(spatialData.minX, spatialData.minY, spatialData.maxX, spatialData.maxY)) { + count++; + } + } + } + + // Count in children if they intersect + if (node.childTL != null) { + if (rect.contains(node.childTL.rect)) { + count += node.childTL.size; + } else if (node.childTL.rect.intersects(rect)) { + count += countNodesInRect(node.childTL, rect); + } + + if (rect.contains(node.childTR.rect)) { + count += node.childTR.size; + } else if (node.childTR.rect.intersects(rect)) { + count += countNodesInRect(node.childTR, rect); + } + + if (rect.contains(node.childBL.rect)) { + count += node.childBL.size; + } else if (node.childBL.rect.intersects(rect)) { + count += countNodesInRect(node.childBL, rect); + } + + if (rect.contains(node.childBR.rect)) { + count += node.childBR.size; + } else if (node.childBR.rect.intersects(rect)) { + count += countNodesInRect(node.childBR, rect); + } + } + + return count; + } + + protected int countNodesInRectApproximate(QuadTreeNode node, Rect2D rect) { + int count = 0; + + // Count objects at this level if the node intersects + if (node.objects != null) { + count += node.objectCount; + } + + // Count in children if they intersect + if (node.childTL != null) { + if (rect.containsOrIntersects(node.childTL.rect)) { + count += node.childTL.size; + } + if (rect.containsOrIntersects(node.childTR.rect)) { + count += node.childTR.size; + } + if (rect.containsOrIntersects(node.childBL.rect)) { + count += node.childBL.size; + } + if (rect.containsOrIntersects(node.childBR.rect)) { + count += node.childBR.size; + } + } + + return count; + } + + @Override + public boolean tryAdvance(Consumer action) { + checkForComodification(); + + if (next != null || findNext()) { + action.accept(next); + next = null; + remainingSize--; + return true; + } + return false; + } + + protected abstract boolean findNext(); + + protected abstract Iterator createIteratorForNode(QuadTreeNode node); + + protected abstract boolean checkElementSpatialMatch(Object element); + + protected abstract AbstractQuadTreeSpliterator createSplitInstance(QuadTreeNode node, Rect2D searchRect, boolean approximate, int expectedVersion, boolean fullyContained, int size); + + @Override + public Spliterator trySplit() { + checkForComodification(); + + // Can only split if we have nodes on the stack + if (!nodesStack.isEmpty() && remainingSize > 1) { + // Take half of the remaining nodes from the stack + int nodesToSplit = Math.min(nodesStack.size() / 2, remainingSize / 2); + if (nodesToSplit > 0) { + Deque splitNodes = new ArrayDeque<>(); + Deque splitContained = new ArrayDeque<>(); + + // Calculate size for the split + int splitSize = 0; + + // Move nodes to split queues and calculate their size + for (int i = 0; i < nodesToSplit; i++) { + QuadTreeNode node = nodesStack.removeLast(); + boolean contained = fullyContainedStack.removeLast(); + splitNodes.addFirst(node); + splitContained.addFirst(contained); + + if (searchRect == null || contained) { + splitSize += node.size; + } else if (approximate) { + splitSize += countNodesInRectApproximate(node, searchRect); + } else { + splitSize += countNodesInRect(node, searchRect); + } + } + + // Update our remaining size + remainingSize -= splitSize; + + // Create new spliterator for the split portion with empty initial state + AbstractQuadTreeSpliterator split = createSplitInstance(null, searchRect, approximate, expectedVersion, false, splitSize); + + // Add all split nodes to the new spliterator's stack + while (!splitNodes.isEmpty()) { + split.nodesStack.push(splitNodes.removeLast()); + split.fullyContainedStack.push(splitContained.removeLast()); + } + + return split; + } + } + return null; + } + + @Override + public long estimateSize() { + return remainingSize; + } + } + + private class QuadTreeNodesSpliterator extends AbstractQuadTreeSpliterator { + + public QuadTreeNodesSpliterator(QuadTreeNode root, Rect2D searchRect, boolean approximate) { + super(root, searchRect, approximate); + } + + private QuadTreeNodesSpliterator(QuadTreeNode node, Rect2D searchRect, boolean approximate, int expectedVersion, boolean fullyContained, int size) { + super(node, searchRect, approximate, expectedVersion, fullyContained, size); + } + + @Override + protected Iterator createIteratorForNode(QuadTreeNode node) { + return node.objects != null ? new ArrayIterator(node.objects, node.objectCount) : null; + } + + @Override + protected boolean checkElementSpatialMatch(Object element) { + NodeImpl elem = (NodeImpl) element; + if (approximate || currentFullyContained || searchRect == null) { + return true; + } else { + final SpatialNodeDataImpl spatialData = elem.getSpatialData(); + return searchRect.intersects(spatialData.minX, spatialData.minY, spatialData.maxX, spatialData.maxY); + } + } + + @Override + protected AbstractQuadTreeSpliterator createSplitInstance(QuadTreeNode node, Rect2D searchRect, boolean approximate, int expectedVersion, boolean fullyContained, int size) { + return new QuadTreeNodesSpliterator(node, searchRect, approximate, expectedVersion, fullyContained, size); + } + + @Override + protected boolean findNext() { + while (currentIterator != null || !nodesStack.isEmpty()) { + if (currentIterator != null) { + while (currentIterator.hasNext()) { + final NodeImpl elem = (NodeImpl) currentIterator.next(); + + if (checkElementSpatialMatch(elem)) { + next = elem; + return true; + } + } + currentIterator = null; + } else { + final QuadTreeNode pointer = nodesStack.pop(); + currentFullyContained = fullyContainedStack + .pop() || (searchRect != null && searchRect.contains(pointer.rect)); + + if (currentFullyContained || searchRect == null || pointer.rect.intersects(searchRect)) { + addNode(pointer, currentFullyContained); + currentIterator = createIteratorForNode(pointer); + } + } + } + return false; + } + + @Override + public int characteristics() { + return DISTINCT | SIZED | SUBSIZED | NONNULL; + } + } + + private abstract static class FilteredSpliterator, P extends Spliterator> implements Spliterator { + protected final S parentSpliterator; + protected final Predicate predicate; + protected final Object[] holder = new Object[1]; + + protected FilteredSpliterator(S parentSpliterator, Predicate predicate) { + this.parentSpliterator = parentSpliterator; + this.predicate = predicate; + } + + protected abstract P createSplitInstance(S splitParent, Predicate predicate); + + protected abstract boolean testPredicate(T element); + + @Override + public boolean tryAdvance(Consumer action) { + while (true) { + boolean advanced = parentSpliterator.tryAdvance(e -> holder[0] = e); + if (!advanced) + return false; + @SuppressWarnings("unchecked") + T t = (T) holder[0]; + if (testPredicate(t)) { + action.accept(t); + return true; + } + } + } + + @Override + @SuppressWarnings("unchecked") + public Spliterator trySplit() { + S splitParent = (S) parentSpliterator.trySplit(); + if (splitParent != null) { + return createSplitInstance(splitParent, predicate); + } + return null; + } + + @Override + public long estimateSize() { + return parentSpliterator.estimateSize(); + } + + @Override + public int characteristics() { + return parentSpliterator.characteristics() & ~(Spliterator.SIZED | Spliterator.SUBSIZED); + } + } + + private class FilteredQuadTreeNodesSpliterator extends FilteredSpliterator { + + public FilteredQuadTreeNodesSpliterator(QuadTreeNode root, Rect2D searchRect, boolean approximate, Predicate predicate) { + super(new QuadTreeNodesSpliterator(root, searchRect, approximate), predicate); + } + + private FilteredQuadTreeNodesSpliterator(QuadTreeNodesSpliterator parentSpliterator, Predicate predicate) { + super(parentSpliterator, predicate); + } + + @Override + protected FilteredQuadTreeNodesSpliterator createSplitInstance(QuadTreeNodesSpliterator splitParent, Predicate predicate) { + return new FilteredQuadTreeNodesSpliterator(splitParent, predicate); + } + + @Override + protected boolean testPredicate(Node element) { + return predicate == null || predicate.test(element); + } + } + + private class QuadTreeEdgesSpliterator extends AbstractQuadTreeSpliterator { + + public QuadTreeEdgesSpliterator(QuadTreeNode root, Rect2D searchRect) { + this(root, searchRect, false); + } + + public QuadTreeEdgesSpliterator(QuadTreeNode root, Rect2D searchRect, boolean approximate) { + super(root, searchRect, approximate); + } + + private QuadTreeEdgesSpliterator(QuadTreeNode node, Rect2D searchRect, boolean approximate, int expectedVersion, boolean fullyContained, int size) { + super(node, searchRect, approximate, expectedVersion, fullyContained, size); + } + + @Override + protected Iterator createIteratorForNode(QuadTreeNode node) { + if (node.objects == null) { + return Collections.emptyIterator(); + } + return graphStore.edgeStore.edgeIterator(new ArrayIterator(node.objects, node.objectCount), false); + } + + @Override + protected boolean checkElementSpatialMatch(Object element) { + Edge edge = (Edge) element; + if (approximate || currentFullyContained || searchRect == null) { + return true; + } else { + // In exact mode, check if edge endpoints intersect with search rect + Node source = edge.getSource(); + Node target = edge.getTarget(); + SpatialNodeDataImpl sourceSpatialData = ((NodeImpl) source).getSpatialData(); + SpatialNodeDataImpl targetSpatialData = ((NodeImpl) target).getSpatialData(); + + return (sourceSpatialData != null && searchRect + .intersects(sourceSpatialData.minX, sourceSpatialData.minY, sourceSpatialData.maxX, sourceSpatialData.maxY)) || (targetSpatialData != null && searchRect + .intersects(targetSpatialData.minX, targetSpatialData.minY, targetSpatialData.maxX, targetSpatialData.maxY)); + } + } + + @Override + protected AbstractQuadTreeSpliterator createSplitInstance(QuadTreeNode node, Rect2D searchRect, boolean approximate, int expectedVersion, boolean fullyContained, int size) { + return new QuadTreeEdgesSpliterator(node, searchRect, approximate, expectedVersion, fullyContained, size); + } + + @Override + protected boolean findNext() { + while (currentIterator != null || !nodesStack.isEmpty()) { + if (currentIterator != null) { + if (currentIterator.hasNext()) { + Edge edge = (Edge) currentIterator.next(); + + if (checkElementSpatialMatch(edge)) { + next = edge; + return true; + } + } else { + currentIterator = null; + } + } else { + final QuadTreeNode pointer = nodesStack.pop(); + currentFullyContained = fullyContainedStack + .pop() || (searchRect != null && searchRect.contains(pointer.rect)); + + if (currentFullyContained || searchRect == null || pointer.rect.intersects(searchRect)) { + addNode(pointer, currentFullyContained); + currentIterator = createIteratorForNode(pointer); + } + } + } + return false; + } + + @Override + public int characteristics() { + return NONNULL; + } + } + + private class FilteredQuadTreeEdgesSpliterator extends FilteredSpliterator { + + public FilteredQuadTreeEdgesSpliterator(QuadTreeNode root, Rect2D searchRect, boolean approximate, Predicate predicate) { + super(new QuadTreeEdgesSpliterator(root, searchRect, approximate), predicate); + } + + private FilteredQuadTreeEdgesSpliterator(QuadTreeEdgesSpliterator parentSpliterator, Predicate predicate) { + super(parentSpliterator, predicate); + } + + @Override + protected FilteredQuadTreeEdgesSpliterator createSplitInstance(QuadTreeEdgesSpliterator splitParent, Predicate predicate) { + return new FilteredQuadTreeEdgesSpliterator(splitParent, predicate); + } + + @Override + protected boolean testPredicate(Edge element) { + return predicate == null || predicate.test(element); + } + } + + /** + * A spliterator that iterates through all edges in the EdgeStore and filters them based on whether their nodes + * belong to quad tree nodes that overlap with a search rectangle. This approach iterates edges directly rather than + * iterating nodes first. + */ + protected class QuadTreeGlobalEdgesSpliterator implements Spliterator { + + private final Rect2D searchRect; + private final boolean approximate; + private final Set overlappingQuadNodes; + private final Spliterator baseSpliterator; + private final int expectedVersion; + private final Predicate additionalPredicate; + + public QuadTreeGlobalEdgesSpliterator(Rect2D searchRect, boolean approximate, Set overlappingQuadNodes, Predicate additionalPredicate) { + this.searchRect = searchRect; + this.approximate = approximate; + this.additionalPredicate = additionalPredicate; + this.expectedVersion = version; + this.overlappingQuadNodes = overlappingQuadNodes; + + // Create the base spliterator from EdgeStore with our predicate + if (additionalPredicate == null) { + if (searchRect == null) { + // No filtering needed, use the full spliterator + this.baseSpliterator = graphStore.edgeStore.spliterator(); + } else { + // Only spatial filtering + this.baseSpliterator = graphStore.edgeStore.newFilteredSpliterator(this::shouldIncludeEdge); + } + } else { + if (searchRect == null) { + this.baseSpliterator = graphStore.edgeStore + .newFilteredSpliterator(this::shouldIncludeEdgeAllWithPredicate); + } else { + this.baseSpliterator = graphStore.edgeStore.newFilteredSpliterator(this::shouldIncludeEdge); + } + } + } + + private QuadTreeGlobalEdgesSpliterator(Rect2D searchRect, boolean approximate, Set overlappingQuadNodes, Spliterator baseSpliterator, int expectedVersion, Predicate additionalPredicate) { + this.searchRect = searchRect; + this.approximate = approximate; + this.overlappingQuadNodes = overlappingQuadNodes; + this.baseSpliterator = baseSpliterator; + this.expectedVersion = expectedVersion; + this.additionalPredicate = additionalPredicate; + } + + private boolean shouldIncludeEdgeAllWithPredicate(EdgeImpl edge) { + checkForComodification(); + + return additionalPredicate.test(edge); + } + + /** + * Determines if an edge should be included based on spatial filtering criteria + */ + private boolean shouldIncludeEdge(EdgeImpl edge) { + checkForComodification(); + + boolean spatialMatch = false; + + SpatialNodeDataImpl sourceSpatialData = edge.source.getSpatialData(); + SpatialNodeDataImpl targetSpatialData = edge.target.getSpatialData(); + + if (sourceSpatialData != null && sourceSpatialData.quadTreeNode != null) { + spatialMatch = overlappingQuadNodes.contains(sourceSpatialData.quadTreeNode); + } + + if (!spatialMatch && targetSpatialData != null && targetSpatialData.quadTreeNode != null) { + // Only check target if source wasn't already overlapping + spatialMatch = overlappingQuadNodes.contains(targetSpatialData.quadTreeNode); + } + + // Apply additional predicate if provided + if (spatialMatch && (additionalPredicate == null || additionalPredicate.test(edge))) { + if (approximate) { + return true; + } else { + // In exact mode, check if edge endpoints intersect with search rect + boolean sourceIntersects = sourceSpatialData != null && searchRect + .intersects(sourceSpatialData.minX, sourceSpatialData.minY, sourceSpatialData.maxX, sourceSpatialData.maxY); + boolean targetIntersects = targetSpatialData != null && searchRect + .intersects(targetSpatialData.minX, targetSpatialData.minY, targetSpatialData.maxX, targetSpatialData.maxY); + return sourceIntersects || targetIntersects; + } + } + return false; + } + + private void checkForComodification() { + if (expectedVersion != version) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean tryAdvance(Consumer action) { + return baseSpliterator.tryAdvance(action); + } + + @Override + public Spliterator trySplit() { + Spliterator splitBase = baseSpliterator.trySplit(); + if (splitBase == null) { + return null; + } + + return new QuadTreeGlobalEdgesSpliterator(searchRect, approximate, overlappingQuadNodes, splitBase, + expectedVersion, additionalPredicate); + } + + @Override + public long estimateSize() { + return baseSpliterator.estimateSize(); + } + + @Override + public int characteristics() { + return baseSpliterator.characteristics(); + } + } +} diff --git a/store/src/main/java/org/gephi/graph/impl/Serialization.java b/src/main/java/org/gephi/graph/impl/Serialization.java similarity index 79% rename from store/src/main/java/org/gephi/graph/impl/Serialization.java rename to src/main/java/org/gephi/graph/impl/Serialization.java index 69487f88..1a4fec99 100644 --- a/store/src/main/java/org/gephi/graph/impl/Serialization.java +++ b/src/main/java/org/gephi/graph/impl/Serialization.java @@ -15,7 +15,7 @@ */ package org.gephi.graph.impl; -import cern.colt.bitvector.BitVector; +import java.util.BitSet; import it.unimi.dsi.fastutil.booleans.BooleanArrayList; import it.unimi.dsi.fastutil.booleans.BooleanOpenHashSet; import it.unimi.dsi.fastutil.bytes.Byte2ObjectOpenHashMap; @@ -24,7 +24,6 @@ import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; import it.unimi.dsi.fastutil.chars.CharArrayList; import it.unimi.dsi.fastutil.chars.CharOpenHashSet; -import it.unimi.dsi.fastutil.doubles.Double2IntMap; import it.unimi.dsi.fastutil.doubles.Double2ObjectOpenHashMap; import it.unimi.dsi.fastutil.doubles.DoubleArrayList; import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet; @@ -49,33 +48,34 @@ import java.io.DataOutput; import java.io.EOFException; import java.io.IOException; +import java.io.UncheckedIOException; import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.Instant; +import java.time.ZoneId; +import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.Spliterator; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import org.gephi.graph.api.Configuration; -import org.gephi.graph.api.Origin; -import org.gephi.graph.api.Estimator; -import org.gephi.graph.api.TimeFormat; -import org.gephi.graph.api.types.TimestampBooleanMap; -import org.gephi.graph.api.types.TimestampByteMap; -import org.gephi.graph.api.types.TimestampCharMap; -import org.gephi.graph.api.types.TimestampDoubleMap; -import org.gephi.graph.api.types.TimestampFloatMap; -import org.gephi.graph.api.types.TimestampIntegerMap; -import org.gephi.graph.api.types.TimestampLongMap; -import org.gephi.graph.api.types.TimestampSet; -import org.gephi.graph.api.types.TimestampShortMap; -import org.gephi.graph.api.types.TimestampStringMap; -import org.gephi.graph.api.types.TimestampMap; import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Estimator; +import org.gephi.graph.api.GraphModel; import org.gephi.graph.api.Interval; import org.gephi.graph.api.Node; +import org.gephi.graph.api.Origin; +import org.gephi.graph.api.TimeFormat; import org.gephi.graph.api.TimeRepresentation; +import org.gephi.graph.api.UnsupportedFormatVersionException; import org.gephi.graph.api.types.IntervalBooleanMap; import org.gephi.graph.api.types.IntervalByteMap; import org.gephi.graph.api.types.IntervalCharMap; @@ -87,11 +87,21 @@ import org.gephi.graph.api.types.IntervalSet; import org.gephi.graph.api.types.IntervalShortMap; import org.gephi.graph.api.types.IntervalStringMap; +import org.gephi.graph.api.types.TimestampBooleanMap; +import org.gephi.graph.api.types.TimestampByteMap; +import org.gephi.graph.api.types.TimestampCharMap; +import org.gephi.graph.api.types.TimestampDoubleMap; +import org.gephi.graph.api.types.TimestampFloatMap; +import org.gephi.graph.api.types.TimestampIntegerMap; +import org.gephi.graph.api.types.TimestampLongMap; +import org.gephi.graph.api.types.TimestampMap; +import org.gephi.graph.api.types.TimestampSet; +import org.gephi.graph.api.types.TimestampShortMap; +import org.gephi.graph.api.types.TimestampStringMap; import org.gephi.graph.impl.EdgeImpl.EdgePropertiesImpl; import org.gephi.graph.impl.NodeImpl.NodePropertiesImpl; import org.gephi.graph.impl.utils.DataInputOutput; import org.gephi.graph.impl.utils.LongPacker; -import org.joda.time.DateTimeZone; // Greatly inspired from JDBM https://github.com/jankotek/JDBM3 public class Serialization { @@ -176,6 +186,8 @@ public class Serialization { final static int STRING_EMPTY = 101; final static int NOTUSED_STRING_255 = 102; final static int STRING = 103; + // Reserved, do not reuse: was java.util.Locale, removed because Locale isn't an + // AttributeUtils supported type. Kept so old streams can still be identified. final static int LOCALE = 124; final static int PROPERTIES = 125; final static int CLASS = 126; @@ -214,6 +226,7 @@ public class Serialization { final static int LIST = 229; final static int SET = 230; final static int MAP = 231; + final static int INSTANT = 232; // Store protected final Int2IntMap idMap; protected GraphModelImpl model; @@ -240,133 +253,360 @@ public void serializeGraphModel(DataOutput out, GraphModelImpl model) throws IOE public GraphModelImpl deserializeGraphModel(DataInput is) throws IOException, ClassNotFoundException { readVersion = (Float) deserialize(is); - Configuration config = (Configuration) deserialize(is); - model = new GraphModelImpl(config); + checkVersionSupported(); + ConfigurationImpl config = (ConfigurationImpl) deserialize(is); + model = new GraphModelImpl(config.toConfiguration()); + deserialize(is); + return model; + } + + public GraphModelImpl deserializeGraphModel(DataInput is, GraphModel graphModel) throws IOException, ClassNotFoundException { + model = (GraphModelImpl) graphModel; + readVersion = (Float) deserialize(is); + checkVersionSupported(); + ConfigurationImpl config = (ConfigurationImpl) deserialize(is); + verifyCompatibility(config, model.configuration); deserialize(is); return model; } + // Fails fast, before any store state is touched, instead of letting a later unrecognized type + // tag surface as a confusing "Unknown serialization type tag" mid-deserialization. + private void checkVersionSupported() throws UnsupportedFormatVersionException { + if (readVersion > VERSION) { + throw new UnsupportedFormatVersionException(readVersion, VERSION); + } + } + + private void verifyCompatibility(ConfigurationImpl readConfig, ConfigurationImpl modelConfig) { + // Time representation + if (!readConfig.getTimeRepresentation().equals(modelConfig.getTimeRepresentation())) { + throw new RuntimeException("The time representations doesn't match, read: " + readConfig + .getTimeRepresentation() + ", model: " + modelConfig.getTimeRepresentation()); + } + + // Node id type + if (!readConfig.getNodeIdType().equals(modelConfig.getNodeIdType())) { + throw new RuntimeException("The node id type doesn't match, read: " + readConfig + .getNodeIdType() + ", model: " + modelConfig.getNodeIdType()); + } + + // Edge id type + if (!readConfig.getEdgeIdType().equals(modelConfig.getEdgeIdType())) { + throw new RuntimeException("The edge id type doesn't match, read: " + readConfig + .getEdgeIdType() + ", model: " + modelConfig.getEdgeIdType()); + } + + // Edge weight type + if (!readConfig.getEdgeWeightType().equals(modelConfig.getEdgeWeightType())) { + throw new RuntimeException("The edge weight type doesn't match, read: " + readConfig + .getEdgeWeightType() + ", model: " + modelConfig.getEdgeWeightType()); + } + + // Edge label type + if (!readConfig.getEdgeLabelType().equals(modelConfig.getEdgeLabelType())) { + throw new RuntimeException("The edge label type doesn't match, read: " + readConfig + .getEdgeLabelType() + ", model: " + modelConfig.getEdgeLabelType()); + } + } + public GraphModelImpl deserializeGraphModelWithoutVersionPrefix(DataInput is, float version) throws IOException, ClassNotFoundException { readVersion = version; - Configuration config = (Configuration) deserialize(is); - model = new GraphModelImpl(config); + checkVersionSupported(); + ConfigurationImpl config = (ConfigurationImpl) deserialize(is); + model = new GraphModelImpl(config.toConfiguration()); deserialize(is); return model; } public void serializeGraphStore(DataOutput out, GraphStore store) throws IOException { - // Configuration - serializeGraphStoreConfiguration(out); + // Hold the read lock for the whole method so the write is atomic with respect to + // concurrent structural mutation. + store.autoReadLock(); + try { + // Configuration + serializeGraphStoreConfiguration(out); + + // GraphVersion + serialize(out, store.version); - // GraphVersion - serialize(out, store.version); + // Edge types + EdgeTypeStore edgeTypeStore = store.edgeTypeStore; + serialize(out, edgeTypeStore); - // Edge types - EdgeTypeStore edgeTypeStore = store.edgeTypeStore; - serialize(out, edgeTypeStore); + // Column + serialize(out, store.nodeTable); + serialize(out, store.edgeTable); - // Column - serialize(out, store.nodeTable); - serialize(out, store.edgeTable); + // Time store + serialize(out, store.timeStore); - // Time store - serialize(out, store.timeStore); + // Factory + serialize(out, store.factory); - // Factory - serialize(out, store.factory); + // Atts + serialize(out, store.attributes); - // Atts - serialize(out, store.attributes); + // TimeFormat + serialize(out, store.timeFormat); - // TimeFormat - serialize(out, store.timeFormat); + // Time zone + serialize(out, store.timeZone); - // Time zone - serialize(out, store.timeZone); + // Nodes + Edges + int nodesAndEdges = store.nodeStore.size() + store.edgeStore.size(); + serialize(out, nodesAndEdges); - // Nodes + Edges - int nodesAndEdges = store.nodeStore.size() + store.edgeStore.size(); - serialize(out, nodesAndEdges); + serializeNodesAndEdges(out, store); - for (Node node : store.nodeStore) { - serialize(out, node); + // Views + serialize(out, store.viewStore); + } finally { + store.autoReadUnlock(); } - for (Edge edge : store.edgeStore) { - serialize(out, edge); + } + + /** + * Writes every node then every edge to out. If either store spans more than one internal storage + * block, encoding is fanned out across worker threads (one thread per block, or groups of blocks); the encoded + * bytes are still written to out from this thread, in the same node-then-edge, block order as the + * plain sequential loop would produce. + */ + private void serializeNodesAndEdges(DataOutput out, GraphStore store) throws IOException { + List> nodeChunks = splitIntoBlockChunks(store.nodeStore.spliterator()); + List> edgeChunks = splitIntoBlockChunks(store.edgeStore.spliterator()); + + if (nodeChunks.size() > 1 || edgeChunks.size() > 1) { + serializeChunksInParallel(out, nodeChunks, edgeChunks); + } else { + serializeChunksSequentially(out, nodeChunks, edgeChunks); } + } - // Views - serialize(out, store.viewStore); + /** + * Recursively decomposes a spliterator into the ordered list of pieces it bottoms out to (one per internal storage + * block, since {@code trySplit()} on the node/edge store spliterators only splits at block boundaries and returns + * null once a piece is a single block). Returns a single-element list, containing root + * unchanged, when there's nothing to split. + */ + static List> splitIntoBlockChunks(Spliterator root) { + List> chunks = new ArrayList<>(); + collectBlockChunks(root, chunks); + return chunks; } - public GraphStore deserializeGraphStore(DataInput is) throws IOException, ClassNotFoundException { - if (!model.store.nodeStore.isEmpty()) { // TODO test other stores - throw new IOException("The store is not empty"); + private static void collectBlockChunks(Spliterator spliterator, List> chunks) { + // trySplit() returns the first half and mutates its receiver into the second half, so the + // recursion order below (left, then the mutated spliterator) preserves original element order. + Spliterator left = spliterator.trySplit(); + if (left == null) { + chunks.add(spliterator); + return; } + collectBlockChunks(left, chunks); + collectBlockChunks(spliterator, chunks); + } - // Store Configuration - deserialize(is); + void serializeChunksSequentially(DataOutput out, List> nodeChunks, List> edgeChunks) throws IOException { + try { + for (Spliterator chunk : nodeChunks) { + chunk.forEachRemaining(node -> writeNodeUnchecked(out, (NodeImpl) node)); + } + for (Spliterator chunk : edgeChunks) { + chunk.forEachRemaining(edge -> writeEdgeUnchecked(out, (EdgeImpl) edge)); + } + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } - // Graph Version - GraphVersion version = (GraphVersion) deserialize(is); - model.store.version.nodeVersion = version.nodeVersion; - model.store.version.edgeVersion = version.edgeVersion; + private void writeNodeUnchecked(DataOutput out, NodeImpl node) { + try { + out.write(NODE); + serializeNode(out, node); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } - // Edge types - deserialize(is); + private void writeEdgeUnchecked(DataOutput out, EdgeImpl edge) { + try { + out.write(EDGE); + serializeEdge(out, edge); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + void serializeChunksInParallel(DataOutput out, List> nodeChunks, List> edgeChunks) throws IOException { + int threadCount = Math.max(1, Runtime.getRuntime().availableProcessors() - 1); + ExecutorService executor = Executors.newFixedThreadPool(threadCount, r -> { + Thread t = new Thread(r, "graphstore-serialize"); + t.setDaemon(true); + return t; + }); + try { + List> tasks = new ArrayList<>(nodeChunks.size() + edgeChunks.size()); + for (Spliterator chunk : nodeChunks) { + tasks.add(() -> encodeNodeChunk(chunk)); + } + for (Spliterator chunk : edgeChunks) { + tasks.add(() -> encodeEdgeChunk(chunk)); + } + drainInOrder(out, executor, tasks, threadCount * 2); + } finally { + executor.shutdownNow(); + } + } - // Columns - deserialize(is); - deserialize(is); + // this/serializeNode/serializeEdge touch no instance state (idMap/model/readVersion are + // deserialize-only), so calling them concurrently from multiple worker threads on the same + // Serialization instance is safe as long as each call writes to its own private buffer. + private DataInputOutput encodeNodeChunk(Spliterator chunk) throws IOException { + DataInputOutput buffer = new DataInputOutput(); + try { + chunk.forEachRemaining(node -> writeNodeUnchecked(buffer, (NodeImpl) node)); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + return buffer; + } - // Time store - deserialize(is); + private DataInputOutput encodeEdgeChunk(Spliterator chunk) throws IOException { + DataInputOutput buffer = new DataInputOutput(); + try { + chunk.forEachRemaining(edge -> writeEdgeUnchecked(buffer, (EdgeImpl) edge)); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + return buffer; + } - // Factory - deserialize(is); + private void drainInOrder(DataOutput out, ExecutorService executor, List> tasks, int maxInFlight) throws IOException { + ArrayDeque> inFlight = new ArrayDeque<>(); + int nextToSubmit = 0; + try { + while (nextToSubmit < Math.min(maxInFlight, tasks.size())) { + inFlight.add(executor.submit(tasks.get(nextToSubmit++))); + } + while (!inFlight.isEmpty()) { + DataInputOutput buffer = inFlight.pollFirst().get(); + out.write(buffer.getBuf(), 0, buffer.getPos()); + if (nextToSubmit < tasks.size()) { + inFlight.add(executor.submit(tasks.get(nextToSubmit++))); + } + } + } catch (ExecutionException e) { + cancelAll(inFlight); + Throwable cause = e.getCause(); + if (cause instanceof IOException) { + throw (IOException) cause; + } else if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof Error) { + throw (Error) cause; + } + throw new IOException(cause); + } catch (InterruptedException e) { + cancelAll(inFlight); + Thread.currentThread().interrupt(); + throw new IOException(e); + } + } + + private static void cancelAll(ArrayDeque> inFlight) { + for (Future future : inFlight) { + future.cancel(true); + } + } - // Atts - GraphAttributesImpl attributes = (GraphAttributesImpl) deserialize(is); - model.store.attributes.setGraphAttributes(attributes); + public GraphStore deserializeGraphStore(DataInput is) throws IOException, ClassNotFoundException { + GraphStore store = model.store; + // Hold the write lock for the whole method: deserialization mutates the store directly + store.autoWriteLock(); + try { + if (!store.nodeStore.isEmpty()) { // TODO test other stores + throw new IOException("The store is not empty"); + } - // TimeFormat - deserialize(is); + idMap.clear(); - // Time zone - deserialize(is); + // Store Configuration + deserialize(is); + + // Graph Version + GraphVersion version = (GraphVersion) deserialize(is); + store.version.nodeVersion = version.nodeVersion; + store.version.edgeVersion = version.edgeVersion; - // Nodes and edges - int nodesAndEdges = (Integer) deserialize(is); - for (int i = 0; i < nodesAndEdges; i++) { + // Edge types deserialize(is); - } - // ViewStore - deserialize(is); + // Columns + deserialize(is); + deserialize(is); + + // Time store + deserialize(is); + + // Factory + deserialize(is); + + // Atts + GraphAttributesImpl attributes = (GraphAttributesImpl) deserialize(is); + store.attributes.setGraphAttributes(attributes); - return model.store; + // TimeFormat + deserialize(is); + + // Time zone + deserialize(is); + + // Nodes and edges + int nodesAndEdges = (Integer) deserialize(is); + for (int i = 0; i < nodesAndEdges; i++) { + deserialize(is); + } + + // ViewStore + deserialize(is); + + return store; + } finally { + store.autoWriteUnlock(); + } } private void serializeNode(DataOutput out, NodeImpl node) throws IOException { serialize(out, node.getId()); - serialize(out, node.storeId); - serialize(out, node.attributes); - serialize(out, node.properties); + writeInteger(out, node.storeId); + serialize(out, node.attributes.attributes); + if (node.properties != null) { + out.write(NODE_PROPERTIES); + serializeNodeProperties(out, node.properties); + } else { + out.write(NULL); + } } private void serializeEdge(DataOutput out, EdgeImpl edge) throws IOException { serialize(out, edge.getId()); - serialize(out, edge.source.storeId); - serialize(out, edge.target.storeId); - serialize(out, edge.type); + writeInteger(out, edge.source.storeId); + writeInteger(out, edge.target.storeId); + writeInteger(out, edge.type); if (edge.graphStore != null && edge.hasDynamicWeight()) { - serialize(out, edge.getWeight()); + writeDouble(out, edge.getWeight()); } else { - serialize(out, GraphStoreConfiguration.DEFAULT_EDGE_WEIGHT); + writeDouble(out, GraphStoreConfiguration.DEFAULT_EDGE_WEIGHT); + } + writeBoolean(out, edge.isDirected()); + serialize(out, edge.attributes.attributes); + if (edge.properties != null) { + out.write(EDGE_PROPERTIES); + serializeEdgeProperties(out, edge.properties); + } else { + out.write(NULL); } - serialize(out, edge.isDirected()); - serialize(out, edge.attributes); - serialize(out, edge.properties); } private NodeImpl deserializeNode(DataInput is) throws IOException, ClassNotFoundException { @@ -376,7 +616,7 @@ private NodeImpl deserializeNode(DataInput is) throws IOException, ClassNotFound NodePropertiesImpl properties = (NodePropertiesImpl) deserialize(is); NodeImpl node = (NodeImpl) model.store.factory.newNode(id); - node.attributes = attributes; + node.attributes.setBackingArray(attributes); if (node.properties != null) { node.setNodeProperties(properties); } @@ -400,15 +640,15 @@ private EdgeImpl deserializeEdge(DataInput is) throws IOException, ClassNotFound int sourceNewId = idMap.get(sourceId); int targetNewId = idMap.get(targetId); - if (sourceId == NULL_ID || targetId == NULL_ID) { - throw new IOException("The edge source of target can't be found"); + if (sourceNewId == NULL_ID || targetNewId == NULL_ID) { + throw new IOException("The edge source or target can't be found"); } NodeImpl source = model.store.nodeStore.get(sourceNewId); NodeImpl target = model.store.nodeStore.get(targetNewId); EdgeImpl edge = (EdgeImpl) model.store.factory.newEdge(id, source, target, type, weight, directed); - edge.attributes = attributes; + edge.attributes.setBackingArray(attributes); if (edge.properties != null) { edge.setEdgeProperties(properties); } @@ -539,24 +779,14 @@ private ColumnImpl deserializeColumn(final DataInput is, TableImpl table) throws boolean readOnly = (Boolean) deserialize(is); Estimator estimator = (Estimator) deserialize(is); - ColumnImpl column = new ColumnImpl(table, (String) id, typeClass, title, defaultValue, origin, indexed, - readOnly); - column.storeId = storeId; - if (estimator != null) { - column.setEstimator(estimator); + ColumnImpl column = model.store.defaultColumns.getColumn(table, storeId); + if (column == null) { + column = new ColumnImpl(table, (String) id, typeClass, title, defaultValue, origin, indexed, readOnly); + column.storeId = storeId; } - // Make sure configured types match the deserialized column types: - if (Edge.class.equals(table.getElementClass())) { - if (id.equals(GraphStoreConfiguration.EDGE_WEIGHT_COLUMN_ID)) { - table.store.configuration.setEdgeWeightType(typeClass); - } else if (id.equals(GraphStoreConfiguration.ELEMENT_ID_COLUMN_ID)) { - table.store.configuration.setEdgeIdType(typeClass); - } - } else if (Node.class.equals(table.getElementClass())) { - if (id.equals(GraphStoreConfiguration.ELEMENT_ID_COLUMN_ID)) { - table.store.configuration.setNodeIdType(typeClass); - } + if (estimator != null) { + column.setEstimator(estimator); } return column; @@ -633,8 +863,8 @@ private GraphViewImpl deserializeGraphView(final DataInput is) throws IOExceptio int storeId = (Integer) deserialize(is); int nodeCount = (Integer) deserialize(is); int edgeCount = (Integer) deserialize(is); - BitVector nodeCountVector = (BitVector) deserialize(is); - BitVector edgeCountVector = (BitVector) deserialize(is); + BitSet nodeCountVector = (BitSet) deserialize(is); + BitSet edgeCountVector = (BitSet) deserialize(is); int[] typeCounts = (int[]) deserialize(is); int[] mutualEdgeTypeCounts = (int[]) deserialize(is); int mutualEdgesCount = (Integer) deserialize(is); @@ -661,23 +891,46 @@ private GraphViewImpl deserializeGraphView(final DataInput is) throws IOExceptio return view; } - private void serializeBitVector(final DataOutput out, final BitVector bitVector) throws IOException { - serialize(out, bitVector.size()); - serialize(out, bitVector.elements()); + // Made compatible with legacy BitVector serialization, which was in place until + // version 0.8.1 + public void serializeBitSet(final DataOutput out, final BitSet bitSet) throws IOException { + // BitSet.length() returns the index of the highest set bit + 1 + // This gives us the logical size (0 if empty) + int size = bitSet.length(); + + serialize(out, size); + + // Get the long array from BitSet + long[] words = bitSet.toLongArray(); + + // Calculate how many longs BitVector would use for this size + int requiredLongs = (size + 63) / 64; + + // Create array with the exact required size (matching BitVector format) + long[] elements = new long[requiredLongs]; + + // Copy the BitSet data + System.arraycopy(words, 0, elements, 0, Math.min(words.length, requiredLongs)); + + serialize(out, elements); } - private BitVector deserializeBitVector(final DataInput is) throws IOException, ClassNotFoundException { + public BitSet deserializeBitSet(final DataInput is) throws IOException, ClassNotFoundException { int size = (Integer) deserialize(is); long[] elements = (long[]) deserialize(is); - return new BitVector(elements, size); + + // BitSet.valueOf() handles the long array correctly + return BitSet.valueOf(elements); } private void serializeGraphStoreConfiguration(final DataOutput out) throws IOException { out.write(GRAPH_STORE_CONFIGURATION); serialize(out, GraphStoreConfiguration.ENABLE_ELEMENT_LABEL); serialize(out, GraphStoreConfiguration.ENABLE_ELEMENT_TIME_SET); - serialize(out, GraphStoreConfiguration.ENABLE_NODE_PROPERTIES); - serialize(out, GraphStoreConfiguration.ENABLE_EDGE_PROPERTIES); + // Was GraphStoreConfiguration.ENABLE_NODE_PROPERTIES + serialize(out, true); + // Was GraphStoreConfiguration.ENABLE_EDGE_PROPERTIES + serialize(out, true); } private GraphStoreConfigurationVersion deserializeGraphStoreConfiguration(final DataInput is) throws IOException, ClassNotFoundException { @@ -752,6 +1005,13 @@ private EdgePropertiesImpl deserializeEdgeProperties(final DataInput is) throws props.rgba = rgba; props.setTextProperties(textProperties); + // Gephi versions before 0.11 used zero alpha to indicate that the element has + // no color + // Override this to avoid hidden elements + if (props.alpha() <= 0f) { + props.setAlpha(1f); + } + return props; } @@ -780,6 +1040,13 @@ private TextPropertiesImpl deserializeTextProperties(final DataInput is) throws props.width = width; props.height = height; + // Gephi versions before 0.11 used zero alpha to indicate that the element has + // no color + // Override this to avoid hidden elements + if (props.getAlpha() <= 0f) { + props.setAlpha(1f); + } + return props; } @@ -910,7 +1177,7 @@ private IntervalMap deserializeIntervalMap(final DataInput is) throws IOExceptio } else if (mapClass.equals(String[].class)) { valueSet = new IntervalStringMap(intervals, (String[]) values); } else { - throw new RuntimeException("Unrecognized timestamp map class"); + throw new RuntimeException("Unrecognized interval map class"); } return valueSet; } @@ -918,11 +1185,13 @@ private IntervalMap deserializeIntervalMap(final DataInput is) throws IOExceptio private void serializeTimestampIndexStore(final DataOutput out, final TimestampIndexStore timestampIndexStore) throws IOException { serialize(out, timestampIndexStore.elementType); - serialize(out, timestampIndexStore.length); - serialize(out, timestampIndexStore.getMap().keySet().toDoubleArray()); - serialize(out, timestampIndexStore.getMap().values().toIntArray()); - serialize(out, timestampIndexStore.garbageQueue.toIntArray()); - serialize(out, timestampIndexStore.countMap); + // The time index is derived state: inserting the nodes and edges rebuilds it. These fields are written empty to + // keep the block layout, which earlier versions read positionally. + serialize(out, 0); + serialize(out, new double[0]); + serialize(out, new int[0]); + serialize(out, new int[0]); + serialize(out, new int[0]); } private TimestampIndexStore deserializeTimestampIndexStore(final DataInput is) throws IOException, ClassNotFoundException { @@ -935,35 +1204,26 @@ private TimestampIndexStore deserializeTimestampIndexStore(final DataInput is) t timestampIndexStore = (TimestampIndexStore) model.store.timeStore.edgeIndexStore; } + // The time index is derived state: inserting the nodes and edges rebuilds it. These fields are read to advance + // the stream and discarded. The casts check each field's type. See serializeTimestampIndexStore for the layout. int length = (Integer) deserialize(is); - double[] doubles = (double[]) deserialize(is); - int[] ints = (int[]) deserialize(is); + double[] timestamps = (double[]) deserialize(is); + int[] timeIndices = (int[]) deserialize(is); int[] garbage = (int[]) deserialize(is); int[] counts = (int[]) deserialize(is); - timestampIndexStore.length = length; - for (int i : garbage) { - timestampIndexStore.garbageQueue.add(i); - } - Double2IntMap m = timestampIndexStore.getMap(); - for (int i = 0; i < ints.length; i++) { - m.put(doubles[i], ints[i]); - } - timestampIndexStore.countMap = counts; return timestampIndexStore; } private void serializeIntervalIndexStore(final DataOutput out, final IntervalIndexStore intervalIndexStore) throws IOException { serialize(out, intervalIndexStore.elementType); - serialize(out, intervalIndexStore.length); - serialize(out, intervalIndexStore.getMap().size()); - for (Map.Entry entry : intervalIndexStore.getMap().entrySet()) { - serialize(out, entry.getKey()); - serialize(out, entry.getValue()); - } - serialize(out, intervalIndexStore.garbageQueue.toIntArray()); - serialize(out, intervalIndexStore.countMap); + // The time index is derived state: inserting the nodes and edges rebuilds it. These fields are written empty to + // keep the block layout, which earlier versions read positionally. The map is written with a zero entry count. + serialize(out, 0); + serialize(out, 0); + serialize(out, new int[0]); + serialize(out, new int[0]); } private IntervalIndexStore deserializeIntervalIndexStore(final DataInput is) throws IOException, ClassNotFoundException { @@ -976,26 +1236,31 @@ private IntervalIndexStore deserializeIntervalIndexStore(final DataInput is) thr intervalIndexStore = (IntervalIndexStore) model.store.timeStore.edgeIndexStore; } + // The time index is derived state: inserting the nodes and edges rebuilds it. These fields are read to advance + // the stream and discarded. The casts check each field's type. See serializeIntervalIndexStore for the layout. int length = (Integer) deserialize(is); int mapSize = (Integer) deserialize(is); - - Interval2IntTreeMap map = intervalIndexStore.getMap(); for (int i = 0; i < mapSize; i++) { - Interval key = (Interval) deserialize(is); - Integer value = (Integer) deserialize(is); - map.put(key, value); + Interval interval = (Interval) deserialize(is); + Integer timeIndex = (Integer) deserialize(is); } int[] garbage = (int[]) deserialize(is); int[] counts = (int[]) deserialize(is); - intervalIndexStore.length = length; - for (int i : garbage) { - intervalIndexStore.garbageQueue.add(i); - } - intervalIndexStore.countMap = counts; return intervalIndexStore; } + private void serializeInstant(final DataOutput out, final Instant instant) throws IOException { + serialize(out, instant.getEpochSecond()); + serialize(out, instant.getNano()); + } + + private Instant deserializeInstant(final DataInput is) throws IOException, ClassNotFoundException { + long epochSecond = (long) deserialize(is); + int nano = (int) deserialize(is); + return Instant.ofEpochSecond(epochSecond, nano); + } + private void serializeGraphAttributes(final DataOutput out, final GraphAttributesImpl graphAttributes) throws IOException { serialize(out, graphAttributes.attributes.size()); for (Map.Entry entry : graphAttributes.attributes.entrySet()) { @@ -1028,14 +1293,14 @@ private TimeFormat deserializeTimeFormat(final DataInput is) throws IOException, return tf; } - private void serializeTimeZone(final DataOutput out, final DateTimeZone timeZone) throws IOException { - serialize(out, timeZone.getID()); + private void serializeTimeZone(final DataOutput out, final ZoneId timeZone) throws IOException { + serialize(out, timeZone.getId()); } - private DateTimeZone deserializeTimeZone(final DataInput is) throws IOException, ClassNotFoundException { + private ZoneId deserializeTimeZone(final DataInput is) throws IOException, ClassNotFoundException { String id = (String) deserialize(is); - DateTimeZone tz = DateTimeZone.forID(id); + ZoneId tz = ZoneId.of(id); model.store.timeZone = tz; return tz; @@ -1069,18 +1334,18 @@ private TimeStore deserializeTimeStore(final DataInput is) throws IOException, C } private void serializeConfiguration(final DataOutput out) throws IOException { - Configuration config = model.store.configuration; + ConfigurationImpl config = model.configuration; serialize(out, config.getNodeIdType()); serialize(out, config.getEdgeIdType()); serialize(out, config.getEdgeLabelType()); serialize(out, config.getEdgeWeightType()); serialize(out, config.getTimeRepresentation()); - serialize(out, config.getEdgeWeightColumn()); + serialize(out, config.isEdgeWeightColumn()); } - private Configuration deserializeConfiguration(final DataInput is) throws IOException, ClassNotFoundException { - Configuration config = new Configuration(); + private ConfigurationImpl deserializeConfiguration(final DataInput is) throws IOException, ClassNotFoundException { + Configuration.Builder config = Configuration.builder(); Class nodeIdType = (Class) deserialize(is); Class edgeIdType = (Class) deserialize(is); @@ -1088,17 +1353,17 @@ private Configuration deserializeConfiguration(final DataInput is) throws IOExce Class edgeWeightType = (Class) deserialize(is); TimeRepresentation timeRepresentation = (TimeRepresentation) deserialize(is); - config.setNodeIdType(nodeIdType); - config.setEdgeIdType(edgeIdType); - config.setEdgeLabelType(edgeLabelType); - config.setEdgeWeightType(edgeWeightType); - config.setTimeRepresentation(timeRepresentation); + config.nodeIdType(nodeIdType); + config.edgeIdType(edgeIdType); + config.edgeLabelType(edgeLabelType); + config.edgeWeightType(edgeWeightType); + config.timeRepresentation(timeRepresentation); if (readVersion >= 0.5) { Boolean edgeColumn = (Boolean) deserialize(is); - config.setEdgeWeightColumn(edgeColumn); + config.edgeWeightColumn(edgeColumn); } - return config; + return new ConfigurationImpl(config.build()); } private void serializeList(final DataOutput out, final List list) throws IOException { @@ -1291,35 +1556,15 @@ protected void serialize(final DataOutput out, final Object obj) throws IOExcept out.write(NULL); } else if (clazz == Boolean.class) { - if (((Boolean) obj)) { - out.write(BOOLEAN_TRUE); - } else { - out.write(BOOLEAN_FALSE); + writeBoolean(out, (Boolean) obj); - } } else if (clazz == Integer.class) { final int val = (Integer) obj; writeInteger(out, val); } else if (clazz == Double.class) { - double v = (Double) obj; - if (v == -1d) { - out.write(DOUBLE_MINUS_1); - } else if (v == 0d) { - out.write(DOUBLE_0); - } else if (v == 1d) { - out.write(DOUBLE_1); - } else if (v >= 0 && v <= 255 && (int) v == v) { - out.write(DOUBLE_255); - out.write((int) v); - } else if (v >= Short.MIN_VALUE && v <= Short.MAX_VALUE && (short) v == v) { - out.write(DOUBLE_SHORT); - out.writeShort((int) v); - } else { - out.write(DOUBLE_FULL); - out.writeDouble(v); + writeDouble(out, (Double) obj); - } } else if (clazz == Float.class) { float v = (Float) obj; if (v == -1f) { @@ -1386,7 +1631,9 @@ protected void serialize(final DataOutput out, final Object obj) throws IOExcept } } else if (clazz == Character.class) { out.write(CHAR); - out.writeChar((Character) obj); + // Write as 2-byte short so the encoding doesn't depend on the DataOutput + // implementation. Byte-identical to DataOutputStream.writeChar(). + out.writeShort((Character) obj); } else if (clazz == String.class) { String s = (String) obj; @@ -1436,7 +1683,8 @@ protected void serialize(final DataOutput out, final Object obj) throws IOExcept char[] a = (char[]) obj; LongPacker.packInt(out, a.length); for (char s : a) { - out.writeChar(s); + // See CHAR above: 2-byte encoding, independent of the DataOutput impl. + out.writeShort(s); } } else if (obj instanceof byte[]) { byte[] b = (byte[]) obj; @@ -1447,12 +1695,6 @@ protected void serialize(final DataOutput out, final Object obj) throws IOExcept out.write(DATE); out.writeLong(((Date) obj).getTime()); - } else if (clazz == Locale.class) { - out.write(LOCALE); - Locale l = (Locale) obj; - out.writeUTF(l.getLanguage()); - out.writeUTF(l.getCountry()); - out.writeUTF(l.getVariant()); } else if (obj instanceof String[]) { String[] b = (String[]) obj; out.write(STRING_ARRAY); @@ -1511,10 +1753,10 @@ protected void serialize(final DataOutput out, final Object obj) throws IOExcept GraphViewImpl b = (GraphViewImpl) obj; out.write(GRAPH_VIEW); serializeGraphView(out, b); - } else if (obj instanceof BitVector) { - BitVector bv = (BitVector) obj; + } else if (obj instanceof BitSet) { + BitSet bs = (BitSet) obj; out.write(BIT_VECTOR); - serializeBitVector(out, bv); + serializeBitSet(out, bs); } else if (obj instanceof GraphVersion) { GraphVersion b = (GraphVersion) obj; out.write(GRAPH_VERSION); @@ -1563,16 +1805,16 @@ protected void serialize(final DataOutput out, final Object obj) throws IOExcept TimeFormat b = (TimeFormat) obj; out.write(TIME_FORMAT); serializeTimeFormat(out, b); - } else if (obj instanceof DateTimeZone) { - DateTimeZone b = (DateTimeZone) obj; + } else if (obj instanceof ZoneId) { + ZoneId b = (ZoneId) obj; out.write(TIME_ZONE); serializeTimeZone(out, b); } else if (obj instanceof TimeStore) { TimeStore b = (TimeStore) obj; out.write(TIME_STORE); serializeTimeStore(out); - } else if (obj instanceof Configuration) { - Configuration b = (Configuration) obj; + } else if (obj instanceof ConfigurationImpl) { + ConfigurationImpl b = (ConfigurationImpl) obj; out.write(CONFIGURATION); serializeConfiguration(out); } else if (obj instanceof Interval) { @@ -1591,6 +1833,10 @@ protected void serialize(final DataOutput out, final Object obj) throws IOExcept Map b = (Map) obj; out.write(MAP); serializeMap(out, b); + } else if (obj instanceof Instant) { + Instant i = (Instant) obj; + out.write(INSTANT); + serializeInstant(out, i); } else { throw new IOException("No serialization handler for this class: " + clazz.getName()); } @@ -1697,6 +1943,29 @@ private void writeIntArray(DataOutput da, int[] obj) throws IOException { } + private void writeBoolean(DataOutput da, final boolean val) throws IOException { + da.write(val ? BOOLEAN_TRUE : BOOLEAN_FALSE); + } + + private void writeDouble(DataOutput da, final double v) throws IOException { + if (v == -1d) { + da.write(DOUBLE_MINUS_1); + } else if (v == 0d) { + da.write(DOUBLE_0); + } else if (v == 1d) { + da.write(DOUBLE_1); + } else if (v >= 0 && v <= 255 && (int) v == v) { + da.write(DOUBLE_255); + da.write((int) v); + } else if (v >= Short.MIN_VALUE && v <= Short.MAX_VALUE && (short) v == v) { + da.write(DOUBLE_SHORT); + da.writeShort((int) v); + } else { + da.write(DOUBLE_FULL); + da.writeDouble(v); + } + } + private void writeInteger(DataOutput da, final int val) throws IOException { if (val == -1) { da.write(INTEGER_MINUS_1); @@ -1935,11 +2204,11 @@ protected Object deserialize(DataInput is) throws IOException, ClassNotFoundExce size = LongPacker.unpackInt(is); ret = new char[size]; for (int i = 0; i < size; i++) { - ((char[]) ret)[i] = is.readChar(); + ((char[]) ret)[i] = (char) is.readUnsignedShort(); } break; case CHAR: - ret = is.readChar(); + ret = Character.valueOf((char) is.readUnsignedShort()); break; case FLOAT_MINUS_1: ret = Float.valueOf(-1); @@ -2028,9 +2297,6 @@ protected Object deserialize(DataInput is) throws IOException, ClassNotFoundExce case ARRAY_BYTE_INT: ret = deserializeArrayByteInt(is); break; - case LOCALE: - ret = new Locale(is.readUTF(), is.readUTF(), is.readUTF()); - break; case STRING_ARRAY: ret = deserializeStringArray(is); break; @@ -2071,7 +2337,7 @@ protected Object deserialize(DataInput is) throws IOException, ClassNotFoundExce ret = deserializeGraphView(is); break; case BIT_VECTOR: - ret = deserializeBitVector(is); + ret = deserializeBitSet(is); break; case GRAPH_STORE_CONFIGURATION: ret = deserializeGraphStoreConfiguration(is); @@ -2133,9 +2399,11 @@ protected Object deserialize(DataInput is) throws IOException, ClassNotFoundExce case MAP: ret = deserializeMap(is); break; - case -1: - throw new EOFException(); - + case INSTANT: + ret = deserializeInstant(is); + break; + default: + throw new IOException("Unknown serialization type tag: " + head); } return ret; } @@ -2310,4 +2578,4 @@ public GraphStoreConfigurationVersion(boolean enableElementLabel, boolean enable this.enableEdgeProperties = enableEdgeProperties; } } -} +} \ No newline at end of file diff --git a/src/main/java/org/gephi/graph/impl/SpatialIndexImpl.java b/src/main/java/org/gephi/graph/impl/SpatialIndexImpl.java new file mode 100644 index 00000000..aac38710 --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/SpatialIndexImpl.java @@ -0,0 +1,117 @@ +package org.gephi.graph.impl; + +import java.util.function.Predicate; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.EdgeIterable; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.NodeIterable; +import org.gephi.graph.api.Rect2D; +import org.gephi.graph.api.SpatialIndex; + +/** + * Graph spatial indexing interface. + * + * @author Eduardo Ramos + */ +public class SpatialIndexImpl implements SpatialIndex { + + protected final NodesQuadTree nodesTree; + + public SpatialIndexImpl(GraphStore store) { + float boundaries = GraphStoreConfiguration.SPATIAL_INDEX_DIMENSION_BOUNDARY; + this.nodesTree = new NodesQuadTree(store, + new Rect2D(-boundaries / 2, -boundaries / 2, boundaries / 2, boundaries / 2)); + } + + @Override + public NodeIterable getNodesInArea(Rect2D rect) { + return nodesTree.getNodes(rect, false); + } + + @Override + public NodeIterable getApproximateNodesInArea(Rect2D rect) { + return nodesTree.getNodes(rect, true); + } + + @Override + public EdgeIterable getEdgesInArea(Rect2D rect) { + return nodesTree.getEdges(rect, false); + } + + @Override + public EdgeIterable getApproximateEdgesInArea(Rect2D rect) { + return nodesTree.getEdges(rect, true); + } + + @Override + public void spatialIndexReadLock() { + nodesTree.readLock(); + } + + @Override + public void spatialIndexReadUnlock() { + nodesTree.readUnlock(); + } + + @Override + public NodeIterable getNodesInArea(Rect2D rect, Predicate predicate) { + return nodesTree.getNodes(rect, false, predicate); + } + + @Override + public NodeIterable getApproximateNodesInArea(Rect2D rect, Predicate predicate) { + return nodesTree.getNodes(rect, true, predicate); + } + + @Override + public EdgeIterable getEdgesInArea(Rect2D rect, Predicate predicate) { + return nodesTree.getEdges(rect, false, predicate); + } + + @Override + public EdgeIterable getApproximateEdgesInArea(Rect2D rect, Predicate predicate) { + return nodesTree.getEdges(rect, true, predicate); + } + + protected void clearNodes() { + nodesTree.clear(); + } + + protected void incrementVersion() { + nodesTree.incrementVersion(); + } + + protected void addNode(final NodeImpl node) { + nodesTree.addNode(node); + } + + protected void removeNode(final NodeImpl node) { + nodesTree.removeNode(node); + } + + protected void moveNode(final NodeImpl node) { + final float x = node.x(); + final float y = node.y(); + final float size = node.size(); + + final float minX = x - size; + final float minY = y - size; + final float maxX = x + size; + final float maxY = y + size; + + nodesTree.updateNode(node, minX, minY, maxX, maxY); + } + + @Override + public Rect2D getBoundaries() { + return nodesTree.getBoundaries(); + } + + public Rect2D getBoundaries(Predicate predicate) { + return nodesTree.getBoundaries(predicate); + } + + public int getObjectCount() { + return nodesTree.getObjectCount(); + } +} diff --git a/src/main/java/org/gephi/graph/impl/SpatialNodeDataImpl.java b/src/main/java/org/gephi/graph/impl/SpatialNodeDataImpl.java new file mode 100644 index 00000000..9e62a5cc --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/SpatialNodeDataImpl.java @@ -0,0 +1,40 @@ +package org.gephi.graph.impl; + +public class SpatialNodeDataImpl { + + public float minX, minY, maxX, maxY; + + protected NodesQuadTree.QuadTreeNode quadTreeNode; + protected int arrayIndex = -1; // Index in the quad tree node's array, -1 if not in a node + + public SpatialNodeDataImpl(float minX, float minY, float maxX, float maxY) { + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + public void updateBoundaries(float minX, float minY, float maxX, float maxY) { + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + public void setQuadTreeNode(NodesQuadTree.QuadTreeNode quadTreeNode) { + this.quadTreeNode = quadTreeNode; + } + + public int getArrayIndex() { + return arrayIndex; + } + + public void setArrayIndex(int arrayIndex) { + this.arrayIndex = arrayIndex; + } + + public void clear() { + this.quadTreeNode = null; + this.arrayIndex = -1; + } +} diff --git a/store/src/main/java/org/gephi/graph/impl/TableImpl.java b/src/main/java/org/gephi/graph/impl/TableImpl.java similarity index 58% rename from store/src/main/java/org/gephi/graph/impl/TableImpl.java rename to src/main/java/org/gephi/graph/impl/TableImpl.java index d4834419..2629de0b 100644 --- a/store/src/main/java/org/gephi/graph/impl/TableImpl.java +++ b/src/main/java/org/gephi/graph/impl/TableImpl.java @@ -15,27 +15,38 @@ */ package org.gephi.graph.impl; +import java.util.Collection; import java.util.Iterator; import java.util.List; import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Column; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Element; +import org.gephi.graph.api.Graph; +import org.gephi.graph.api.Node; import org.gephi.graph.api.Origin; import org.gephi.graph.api.Table; import org.gephi.graph.api.TableObserver; -import org.gephi.graph.api.Element; -import org.gephi.graph.api.Graph; -public class TableImpl implements Table { +public class TableImpl implements Collection, Table { // Store protected final ColumnStore store; + // Configuration + protected final ConfigurationImpl configuration; - public TableImpl(Class elementType, boolean indexed) { - this(null, elementType, indexed); + public TableImpl(Class elementType) { + this(null, elementType); } - public TableImpl(GraphStore graphStore, Class elementType, boolean indexed) { - store = new ColumnStore<>(graphStore, elementType, indexed); + public TableImpl(GraphStore graphStore, Class elementType) { + store = new ColumnStore<>(graphStore, elementType); + if (graphStore == null) { + // Used for testing only + configuration = new ConfigurationImpl(); + } else { + configuration = graphStore.configuration; + } } @Override @@ -58,6 +69,7 @@ public Column addColumn(String id, String title, Class type, Origin origin, Obje checkValidId(id); checkSupportedTypes(type); checkDefaultValue(defaultValue, type); + checkCanIndex(indexed); type = AttributeUtils.getStandardizedType(type); if (defaultValue != null) { @@ -80,11 +92,33 @@ public Column addColumn(String id, String title, Class type, Origin origin, Obje return column; } + @Override + public boolean add(Column column) { + store.checkNonNullColumnObject(column); + store.addColumn(column); + return true; + } + @Override public int countColumns() { return store.size(); } + @Override + public int countColumns(Origin origin) { + return store.size(origin); + } + + @Override + public int size() { + return countColumns(); + } + + @Override + public boolean isEmpty() { + return countColumns() == 0; + } + @Override public Iterator iterator() { return store.iterator(); @@ -100,26 +134,51 @@ public Column[] toArray() { return store.toArray(); } + @Override + public K[] toArray(K[] array) { + store.checkNonNullObject(array); + + ColumnImpl[] columns = store.toArray(); + + if (array.length < size()) { + array = (K[]) java.lang.reflect.Array.newInstance(array.getClass().getComponentType(), size()); + } + for (int i = 0; i < columns.length; i++) { + array[i] = (K) columns[i]; + } + return array; + } + @Override public List toList() { return store.toList(); } @Override - public Column getColumn(int index) { + public ColumnImpl getColumn(int index) { return store.getColumnByIndex(index); } @Override - public Column getColumn(String id) { + public ColumnImpl getColumn(String id) { + store.checkNonNullObject(id); return store.getColumn(id.toLowerCase()); } @Override public boolean hasColumn(String id) { + store.checkNonNullObject(id); return store.hasColumn(id.toLowerCase()); } + @Override + public boolean contains(Object o) { + store.checkNonNullColumnObject(o); + + ColumnImpl column = (ColumnImpl) o; + return hasColumn(column.getId()); + } + @Override public void removeColumn(Column column) { store.removeColumn(column); @@ -127,9 +186,42 @@ public void removeColumn(Column column) { @Override public void removeColumn(String id) { + store.checkNonNullObject(id); store.removeColumn(id.toLowerCase()); } + @Override + public boolean remove(Object o) { + store.checkNonNullColumnObject(o); + removeColumn((ColumnImpl) o); + return true; + } + + @Override + public void clear() { + throw new UnsupportedOperationException("This method from Collection isn't implemented"); + } + + @Override + public boolean containsAll(Collection c) { + throw new UnsupportedOperationException("This method from Collection isn't implemented"); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException("This method from Collection isn't implemented"); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException("This method from Collection isn't implemented"); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException("This method from Collection isn't implemented"); + } + @Override public TableObserver createTableObserver(boolean withDiff) { return store.createTableObserver(this, withDiff); @@ -145,6 +237,21 @@ public Graph getGraph() { return store.graphStore; } + @Override + public boolean isNodeTable() { + return Node.class.equals(store.elementType); + } + + @Override + public boolean isEdgeTable() { + return Edge.class.equals(store.elementType); + } + + @Override + public TableLockImpl getLock() { + return store.lock; + } + public void destroyTableObserver(TableObserver observer) { checkableTableObserver(observer); @@ -182,6 +289,12 @@ private void checkSupportedTypes(Class type) { } } + private void checkCollection(final Collection collection) { + if (collection == this) { + throw new IllegalArgumentException("Can't pass itself"); + } + } + private void checkDefaultValue(Object defaultValue, Class type) { if (defaultValue != null) { if (defaultValue.getClass() != type) { @@ -190,6 +303,19 @@ private void checkDefaultValue(Object defaultValue, Class type) { } } + private void checkCanIndex(boolean indexed) { + if (indexed && store.graphStore != null) { + if (!store.graphStore.configuration.isEnableIndexNodes() && isNodeTable()) { + throw new IllegalArgumentException( + "Can't use reverse index as node indexing is disabled (from Configuration)"); + } + if (!store.graphStore.configuration.isEnableIndexEdges() && isEdgeTable()) { + throw new IllegalArgumentException( + "Can't index edge table as edge indexing is disabled (from Configuration)"); + } + } + } + private void checkableTableObserver(TableObserver observer) { if (observer == null) { throw new NullPointerException(); diff --git a/store/src/main/java/org/gephi/graph/impl/TableLock.java b/src/main/java/org/gephi/graph/impl/TableLockImpl.java similarity index 78% rename from store/src/main/java/org/gephi/graph/impl/TableLock.java rename to src/main/java/org/gephi/graph/impl/TableLockImpl.java index a618d024..cbc83839 100644 --- a/store/src/main/java/org/gephi/graph/impl/TableLock.java +++ b/src/main/java/org/gephi/graph/impl/TableLockImpl.java @@ -16,20 +16,28 @@ package org.gephi.graph.impl; import java.util.concurrent.locks.ReentrantLock; +import org.gephi.graph.api.TableLock; -public class TableLock { +public class TableLockImpl implements TableLock { protected final ReentrantLock lock; - public TableLock() { + public TableLockImpl() { lock = new ReentrantLock(); } + @Override public void lock() { lock.lock(); } + @Override public void unlock() { lock.unlock(); } + + @Override + public int getHoldCount() { + return lock.getHoldCount(); + } } diff --git a/store/src/main/java/org/gephi/graph/impl/TableObserverImpl.java b/src/main/java/org/gephi/graph/impl/TableObserverImpl.java similarity index 100% rename from store/src/main/java/org/gephi/graph/impl/TableObserverImpl.java rename to src/main/java/org/gephi/graph/impl/TableObserverImpl.java diff --git a/store/src/main/java/org/gephi/graph/impl/TextPropertiesImpl.java b/src/main/java/org/gephi/graph/impl/TextPropertiesImpl.java similarity index 100% rename from store/src/main/java/org/gephi/graph/impl/TextPropertiesImpl.java rename to src/main/java/org/gephi/graph/impl/TextPropertiesImpl.java diff --git a/store/src/main/java/org/gephi/graph/impl/TimeAttributeIterable.java b/src/main/java/org/gephi/graph/impl/TimeAttributeIterable.java similarity index 100% rename from store/src/main/java/org/gephi/graph/impl/TimeAttributeIterable.java rename to src/main/java/org/gephi/graph/impl/TimeAttributeIterable.java diff --git a/store/src/main/java/org/gephi/graph/impl/TimeIndexImpl.java b/src/main/java/org/gephi/graph/impl/TimeIndexImpl.java similarity index 59% rename from store/src/main/java/org/gephi/graph/impl/TimeIndexImpl.java rename to src/main/java/org/gephi/graph/impl/TimeIndexImpl.java index 4514e49a..965ede2d 100644 --- a/store/src/main/java/org/gephi/graph/impl/TimeIndexImpl.java +++ b/src/main/java/org/gephi/graph/impl/TimeIndexImpl.java @@ -15,13 +15,11 @@ */ package org.gephi.graph.impl; -import it.unimi.dsi.fastutil.objects.ObjectIterator; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectSet; -import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; -import java.util.List; +import java.util.Set; import org.gephi.graph.api.Element; import org.gephi.graph.api.ElementIterable; import org.gephi.graph.api.TimeIndex; @@ -31,7 +29,7 @@ public abstract class TimeIndexImpl, M extends TimeMap> implements TimeIndex { // Data - protected final GraphLock lock; + protected final TableLockImpl lock; protected final TimeIndexStore timestampIndexStore; protected final boolean mainIndex; protected TimeIndexEntry[] timestamps; @@ -41,7 +39,7 @@ protected TimeIndexImpl(TimeIndexStore store, boolean main) { timestampIndexStore = store; mainIndex = main; timestamps = new TimeIndexEntry[0]; - lock = store.graphLock; + lock = store.lock; } public boolean hasElements() { @@ -49,32 +47,50 @@ public boolean hasElements() { } public void clear() { - timestamps = new TimeIndexEntry[0]; - elementCount = 0; + lock(); + try { + timestamps = new TimeIndexEntry[0]; + elementCount = 0; + } finally { + unlock(); + } } protected void add(int timestampIndex, Element element) { - ensureArraySize(timestampIndex); - TimeIndexEntry entry = timestamps[timestampIndex]; - if (entry == null) { - entry = addTimestamp(timestampIndex); - } - if (entry.add(element)) { - elementCount++; + lock(); + try { + ensureArraySize(timestampIndex); + TimeIndexEntry entry = timestamps[timestampIndex]; + if (entry == null) { + entry = addTimestamp(timestampIndex); + } + if (entry.add(element)) { + elementCount++; + } + } finally { + unlock(); } } protected void remove(int timestampIndex, Element element) { - TimeIndexEntry entry = timestamps[timestampIndex]; - if (entry.remove(element)) { - elementCount--; - if (entry.isEmpty()) { - clearEntry(timestampIndex); + lock(); + try { + if (timestampIndex >= timestamps.length) { + return; } + TimeIndexEntry entry = timestamps[timestampIndex]; + if (entry != null && entry.remove(element)) { + elementCount--; + if (entry.isEmpty()) { + clearEntry(timestampIndex); + } + } + } finally { + unlock(); } } - protected TimeIndexEntry addTimestamp(final int index) { + private TimeIndexEntry addTimestamp(final int index) { ensureArraySize(index); TimeIndexEntry entry = new TimeIndexEntry(); timestamps[index] = entry; @@ -99,27 +115,15 @@ protected void checkDouble(double timestamp) { } } - protected void readLock() { + protected void lock() { if (lock != null) { - lock.readLock(); + lock.lock(); } } - protected void readUnlock() { + protected void unlock() { if (lock != null) { - lock.readUnlock(); - } - } - - protected void writeLock() { - if (lock != null) { - lock.writeLock(); - } - } - - protected void writeUnlock() { - if (lock != null) { - lock.writeUnlock(); + lock.unlock(); } } @@ -144,68 +148,36 @@ public boolean isEmpty() { } } - protected class ElementIteratorImpl implements Iterator { - - private final ObjectIterator itr; - - public ElementIteratorImpl(ObjectIterator itr) { - this.itr = itr; - } - - @Override - public boolean hasNext() { - final boolean hasNext = itr.hasNext(); - if (!hasNext) { - readUnlock(); - } - return hasNext; - } - - @Override - public Element next() { - return itr.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Not supported."); - } - } - - protected class ElementIterableImpl implements ElementIterable { + protected class ElementSetWrapperIterable implements ElementIterable { - protected final Iterator iterator; + protected final Set set; - public ElementIterableImpl(Iterator iterator) { - this.iterator = iterator; + public ElementSetWrapperIterable(Set set) { + this.set = set; } @Override public Iterator iterator() { - return iterator; + return set.iterator(); } @Override public Element[] toArray() { - List list = new ArrayList<>(); - for (; iterator.hasNext();) { - list.add(iterator.next()); - } - return list.toArray(new Element[0]); + return set.toArray(new Element[0]); } @Override public Collection toCollection() { - List list = new ArrayList<>(); - for (; iterator.hasNext();) { - list.add(iterator.next()); - } - return list; + return set; + } + + @Override + public Set toSet() { + return set; } @Override public void doBreak() { - readUnlock(); } } } diff --git a/store/src/main/java/org/gephi/graph/impl/TimeIndexStore.java b/src/main/java/org/gephi/graph/impl/TimeIndexStore.java similarity index 52% rename from store/src/main/java/org/gephi/graph/impl/TimeIndexStore.java rename to src/main/java/org/gephi/graph/impl/TimeIndexStore.java index bb3bebc4..c247aac4 100644 --- a/store/src/main/java/org/gephi/graph/impl/TimeIndexStore.java +++ b/src/main/java/org/gephi/graph/impl/TimeIndexStore.java @@ -35,10 +35,10 @@ public abstract class TimeIndexStore, M extends TimeMap> { // Lock - protected final GraphLock graphLock; + protected final TableLockImpl lock; // Element protected final Class elementType; - // Timestamp index managament + // Timestamp index management protected final Map timeSortedMap; protected final IntSortedSet garbageQueue; protected int[] countMap; @@ -47,9 +47,9 @@ public abstract class TimeIndexStore, protected TimeIndexImpl mainIndex; protected final Map viewIndexes; - protected TimeIndexStore(Class type, GraphLock lock, boolean indexed, Map sortedMap) { - elementType = type; - graphLock = lock; + protected TimeIndexStore(Class type, TableLockImpl lock, boolean indexed, Map sortedMap) { + this.elementType = type; + this.lock = lock; garbageQueue = new IntRBTreeSet(); // Subclass @@ -65,47 +65,54 @@ protected TimeIndexStore(Class type, GraphLock lock, boolean indexed, Map entry : viewIndexes.entrySet()) { - GraphViewImpl graphView = (GraphViewImpl) entry.getKey(); - DirectedSubgraph graph = graphView.getDirectedGraph(); - boolean node = element instanceof Node; - if (node ? graph.contains((Node) element) : graph.contains((Edge) element)) { - entry.getValue().add(timeIndex, element); + if (!viewIndexes.isEmpty()) { + for (Entry entry : viewIndexes.entrySet()) { + GraphViewImpl graphView = (GraphViewImpl) entry.getKey(); + boolean node = element instanceof Node; + if (node ? graphView.containsNode((Node) element) : graphView.containsEdge((Edge) element)) { + entry.getValue().add(timeIndex, element); + } } - } + } } + } finally { + unlock(); } - - return timeIndex; } public void add(TimeMap timeMap) { @@ -114,48 +121,59 @@ public void add(TimeMap timeMap) { } } - public void add(TimeSet timeSet) { + public void add(TimeSet timeSet, Element element) { for (K timeKey : timeSet.toArray()) { - add(timeKey); + add(timeKey, element); } } - public Integer remove(K k) { - checkK(k); + protected Integer remove(K k) { + lock(); + try { + checkK(k); - Integer id = timeSortedMap.get(k); - if (id != null) { - if (--countMap[id] == 0) { - garbageQueue.add(id); - timeSortedMap.remove(k); + Integer id = timeSortedMap.get(k); + if (id != null) { + if (--countMap[id] == 0) { + garbageQueue.add(id); + timeSortedMap.remove(k); + } } + return id; + } finally { + unlock(); } - return id; } - public int remove(K k, Element element) { - Integer timeIndex = remove(k); - checkTimeIndex(timeIndex); + public void remove(K k, Element element) { + lock(); + try { + Integer timeIndex = remove(k); + if (timeIndex == null) { + return; + } - if (mainIndex != null) { - mainIndex.remove(timeIndex, element); + if (mainIndex != null) { + mainIndex.remove(timeIndex, element); - if (!viewIndexes.isEmpty()) { - for (Entry entry : viewIndexes.entrySet()) { - GraphViewImpl graphView = (GraphViewImpl) entry.getKey(); - DirectedSubgraph graph = graphView.getDirectedGraph(); - if (element instanceof Node) { - if (graph.contains((Node) element)) { + if (!viewIndexes.isEmpty()) { + for (Entry entry : viewIndexes.entrySet()) { + GraphViewImpl graphView = (GraphViewImpl) entry.getKey(); + DirectedSubgraph graph = graphView.getDirectedGraph(); + if (element instanceof Node) { + if (graph.contains((Node) element)) { + entry.getValue().remove(timeIndex, element); + } + } else if (graph.contains((Edge) element)) { entry.getValue().remove(timeIndex, element); } - } else if (graph.contains((Edge) element)) { - entry.getValue().remove(timeIndex, element); } } } - } - return timeIndex; + } finally { + unlock(); + } } public void remove(M timeMap) { @@ -164,94 +182,82 @@ public void remove(M timeMap) { } } - public void remove(S timeSet) { + public void remove(S timeSet, Element element) { for (K timeKey : timeSet.toArray()) { - remove(timeKey); + remove(timeKey, element); } } public boolean contains(K k) { checkK(k); - return timeSortedMap.containsKey(k); + lock(); + try { + return timeSortedMap.containsKey(k); + } finally { + unlock(); + } } public void index(Element element) { - S timeSet = getTimeSet(element); - - if (timeSet != null) { - add(timeSet); - } - - for (Object val : element.getAttributes()) { - if (val != null && val instanceof TimeMap) { - TimeMap dynamicValue = (TimeMap) val; - add(dynamicValue); - } - } - - if (timeSet != null && mainIndex != null) { - K[] ts = timeSet.toArray(); - int tsLength = ts.length; - for (int i = 0; i < tsLength; i++) { - int timestampIndex = timeSortedMap.get(ts[i]); - mainIndex.add(timestampIndex, element); + synchronized (element) { + S timeSet = getTimeSet(element); + lock(); + try { + if (timeSet != null) { + add(timeSet, element); + } + for (Object val : element.getAttributes()) { + if (val instanceof TimeMap) { + TimeMap dynamicValue = (TimeMap) val; + add(dynamicValue); + } + } + } finally { + unlock(); } } } public void clear(Element element) { - S timeSet = getTimeSet(element); - - if (timeSet != null && mainIndex != null) { - K[] ts = timeSet.toArray(); - int tsLength = ts.length; - for (int i = 0; i < tsLength; i++) { - int timestampIndex = timeSortedMap.get(ts[i]); - mainIndex.remove(timestampIndex, element); - } - - if (!viewIndexes.isEmpty()) { - for (Entry entry : viewIndexes.entrySet()) { - GraphViewImpl graphView = (GraphViewImpl) entry.getKey(); - DirectedSubgraph graph = graphView.getDirectedGraph(); - boolean node = element instanceof Node; - if (node ? graph.contains((Node) element) : graph.contains((Edge) element)) { - for (int i = 0; i < tsLength; i++) { - int timestampIndex = timeSortedMap.get(ts[i]); - entry.getValue().remove(timestampIndex, element); - } + synchronized (element) { + S timeSet = getTimeSet(element); + lock(); + try { + if (timeSet != null) { + remove(timeSet, element); + } + for (Object val : element.getAttributes()) { + if (val instanceof TimeMap) { + TimeMap dynamicValue = (TimeMap) val; + remove((M) dynamicValue); } } - } - } - - if (timeSet != null) { - remove(timeSet); - } - - for (Object val : element.getAttributes()) { - if (val != null && val instanceof TimeMap) { - TimeMap dynamicValue = (TimeMap) val; - remove((M) dynamicValue); + } finally { + unlock(); } } } public void clear() { - timeSortedMap.clear(); - garbageQueue.clear(); - countMap = new int[0]; - length = 0; - - if (mainIndex != null) { - mainIndex.clear(); - - if (!viewIndexes.isEmpty()) { - for (TimeIndexImpl index : viewIndexes.values()) { - index.clear(); + lock(); + try { + timeSortedMap.clear(); + garbageQueue.clear(); + countMap = new int[0]; + length = 0; + + if (mainIndex != null) { + mainIndex.clear(); + + if (!viewIndexes.isEmpty()) { + for (TimeIndexImpl index : viewIndexes.values()) { + index.clear(); + } } } + } finally { + unlock(); } } @@ -264,13 +270,20 @@ public TimeIndex getIndex(Graph graph) { if (view.isMainView()) { return mainIndex; } - TimeIndexImpl viewIndex = viewIndexes.get(graph.getView()); - if (viewIndex == null) { - // TODO Make the auto-creation optional? - viewIndex = createViewIndex(graph); - viewIndexes.put(graph.getView(), viewIndex); + if (viewIndexes == null) { + return null; + } + lock(); + try { + TimeIndexImpl viewIndex = viewIndexes.get(graph.getView()); + if (viewIndex == null) { + // TODO Make the auto-creation optional? + viewIndex = createViewIndex(graph); + } + return viewIndex; + } finally { + unlock(); } - return viewIndex; } protected TimeIndexImpl createViewIndex(Graph graph) { @@ -291,9 +304,14 @@ public void deleteViewIndex(Graph graph) { if (graph.getView().isMainView()) { throw new IllegalArgumentException("Can't delete a view index for the main view"); } - TimeIndexImpl index = viewIndexes.remove(graph.getView()); - if (index != null) { - index.clear(); + lock(); + try { + TimeIndexImpl index = viewIndexes.remove(graph.getView()); + if (index != null) { + index.clear(); + } + } finally { + unlock(); } } @@ -301,6 +319,7 @@ public void indexView(Graph graph) { TimeIndexImpl viewIndex = viewIndexes.get(graph.getView()); if (viewIndex != null) { graph.readLock(); + lock(); try { Iterator iterator = null; @@ -318,14 +337,17 @@ public void indexView(Graph graph) { K[] ts = set.toArray(); int tsLength = ts.length; for (int i = 0; i < tsLength; i++) { - int timestamp = timeSortedMap.get(ts[i]); - viewIndex.add(timestamp, element); + Integer timestamp = timeSortedMap.get(ts[i]); + if (timestamp != null) { + viewIndex.add(timestamp, element); + } } } } } } finally { graph.readUnlock(); + unlock(); } } } @@ -333,30 +355,43 @@ public void indexView(Graph graph) { public void indexInView(T element, GraphView view) { TimeIndexImpl viewIndex = viewIndexes.get(view); if (viewIndex != null) { - S set = getTimeSet(element); - if (set != null) { - K[] ts = set.toArray(); - int tsLength = ts.length; - for (int i = 0; i < tsLength; i++) { - int timestampIndex = timeSortedMap.get(ts[i]); - viewIndex.add(timestampIndex, element); + lock(); + try { + S set = getTimeSet(element); + if (set != null) { + K[] ts = set.toArray(); + int tsLength = ts.length; + for (int i = 0; i < tsLength; i++) { + Integer timestampIndex = timeSortedMap.get(ts[i]); + if (timestampIndex != null) { + viewIndex.add(timestampIndex, element); + } + } } + } finally { + unlock(); } } } public void clearInView(T element, GraphView view) { - ElementImpl elementImpl = (ElementImpl) element; TimeIndexImpl viewIndex = viewIndexes.get(view); if (viewIndex != null) { - S set = getTimeSet(element); - if (set != null) { - K[] ts = set.toArray(); - int tsLength = ts.length; - for (int i = 0; i < tsLength; i++) { - int timestampIndex = timeSortedMap.get(ts[i]); - viewIndex.remove(timestampIndex, elementImpl); + lock(); + try { + S set = getTimeSet(element); + if (set != null) { + K[] ts = set.toArray(); + int tsLength = ts.length; + for (int i = 0; i < tsLength; i++) { + Integer timestampIndex = timeSortedMap.get(ts[i]); + if (timestampIndex != null) { + viewIndex.remove(timestampIndex, element); + } + } } + } finally { + unlock(); } } } @@ -388,8 +423,8 @@ private void checkTimeIndex(Integer timeIndex) { protected void ensureArraySize(int index) { if (index >= countMap.length) { - int newSize = Math - .min(Math.max(index + 1, (int) (index * GraphStoreConfiguration.TIMESTAMP_STORE_GROWING_FACTOR)), Integer.MAX_VALUE); + int newSize = Math.min(Math + .max(index + 1, (int) (index * GraphStoreConfiguration.TIMESTAMP_STORE_GROWING_FACTOR)), Integer.MAX_VALUE); int[] newArray = new int[newSize]; System.arraycopy(countMap, 0, newArray, 0, countMap.length); countMap = newArray; @@ -414,7 +449,7 @@ public boolean deepEquals(TimeIndexStore obj) { if (!obj.getClass().equals(getClass())) { return false; } - TimeIndexStore other = (TimeIndexStore) obj; + TimeIndexStore other = obj; if (!other.elementType.equals(elementType)) { return false; } @@ -429,4 +464,16 @@ public boolean deepEquals(TimeIndexStore obj) { } return true; } + + private void lock() { + if (lock != null) { + lock.lock(); + } + } + + private void unlock() { + if (lock != null) { + lock.unlock(); + } + } } diff --git a/store/src/main/java/org/gephi/graph/impl/TimeStore.java b/src/main/java/org/gephi/graph/impl/TimeStore.java similarity index 75% rename from store/src/main/java/org/gephi/graph/impl/TimeStore.java rename to src/main/java/org/gephi/graph/impl/TimeStore.java index 4d91a934..292894bc 100644 --- a/store/src/main/java/org/gephi/graph/impl/TimeStore.java +++ b/src/main/java/org/gephi/graph/impl/TimeStore.java @@ -23,15 +23,15 @@ public class TimeStore { protected final GraphStore graphStore; - // Lock (optional - protected final GraphLock lock; + // Lock (optional) + protected final TableLockImpl lock; // Store - protected TimeIndexStore nodeIndexStore; - protected TimeIndexStore edgeIndexStore; + protected final TimeIndexStore nodeIndexStore; + protected final TimeIndexStore edgeIndexStore; - public TimeStore(GraphStore store, GraphLock graphLock, boolean indexed) { - lock = graphLock; - graphStore = store; + public TimeStore(GraphStore store, boolean indexed) { + this.graphStore = store; + this.lock = store != null && store.configuration.isEnableAutoLocking() ? new TableLockImpl() : null; TimeRepresentation timeRepresentation = GraphStoreConfiguration.DEFAULT_TIME_REPRESENTATION; if (store != null) { @@ -46,21 +46,8 @@ public TimeStore(GraphStore store, GraphLock graphLock, boolean indexed) { } } - protected void resetConfiguration() { - if (graphStore != null) { - if (graphStore.configuration.getTimeRepresentation().equals(TimeRepresentation.INTERVAL)) { - nodeIndexStore = new IntervalIndexStore<>(Node.class, lock, nodeIndexStore.hasIndex()); - edgeIndexStore = new IntervalIndexStore<>(Edge.class, lock, edgeIndexStore.hasIndex()); - } else { - nodeIndexStore = new TimestampIndexStore<>(Node.class, lock, nodeIndexStore.hasIndex()); - edgeIndexStore = new TimestampIndexStore<>(Edge.class, lock, edgeIndexStore.hasIndex()); - } - } - } - public double getMin(Graph graph) { - if (nodeIndexStore == null || edgeIndexStore == null) { - // TODO: Manual calculation + if (!nodeIndexStore.hasIndex() || !edgeIndexStore.hasIndex()) { return Double.NEGATIVE_INFINITY; } double nodeMin = nodeIndexStore.getIndex(graph).getMinTimestamp(); @@ -75,8 +62,7 @@ public double getMin(Graph graph) { } public double getMax(Graph graph) { - if (nodeIndexStore == null || edgeIndexStore == null) { - // TODO: Manual calculation + if (!nodeIndexStore.hasIndex() || !edgeIndexStore.hasIndex()) { return Double.POSITIVE_INFINITY; } double nodeMax = nodeIndexStore.getIndex(graph).getMaxTimestamp(); diff --git a/src/main/java/org/gephi/graph/impl/TimestampIndexImpl.java b/src/main/java/org/gephi/graph/impl/TimestampIndexImpl.java new file mode 100644 index 00000000..4f0e3dd0 --- /dev/null +++ b/src/main/java/org/gephi/graph/impl/TimestampIndexImpl.java @@ -0,0 +1,158 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import it.unimi.dsi.fastutil.doubles.Double2IntMap; +import it.unimi.dsi.fastutil.doubles.Double2IntSortedMap; +import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import org.gephi.graph.api.Element; +import org.gephi.graph.api.ElementIterable; +import org.gephi.graph.api.Interval; +import org.gephi.graph.api.types.TimestampMap; +import org.gephi.graph.api.types.TimestampSet; + +public class TimestampIndexImpl extends TimeIndexImpl> { + + public TimestampIndexImpl(TimeIndexStore> store, boolean main) { + super(store, main); + } + + @Override + public double getMinTimestamp() { + lock(); + try { + Double2IntSortedMap sortedMap = (Double2IntSortedMap) timestampIndexStore.timeSortedMap; + if (mainIndex) { + // Returns the minimum across all tracked timestamps, including those that + // belong only to dynamic attribute values (TimeMap columns) and not to + // element existence (TimeSet). This intentionally gives the earliest time + // at which any graph data exists, which may be earlier than the first time + // any element is present. View indexes filter to element-only timestamps. + if (!sortedMap.isEmpty()) { + return sortedMap.firstDoubleKey(); + } + } else { + if (!sortedMap.isEmpty()) { + ObjectBidirectionalIterator bi = sortedMap.double2IntEntrySet().iterator(); + while (bi.hasNext()) { + Double2IntMap.Entry entry = bi.next(); + double timestamp = entry.getDoubleKey(); + int index = entry.getIntValue(); + if (index < timestamps.length) { + TimeIndexEntry timestampEntry = timestamps[index]; + if (timestampEntry != null) { + return timestamp; + } + } + } + } + } + return Double.NEGATIVE_INFINITY; + } finally { + unlock(); + } + } + + @Override + public double getMaxTimestamp() { + lock(); + try { + Double2IntSortedMap sortedMap = (Double2IntSortedMap) timestampIndexStore.timeSortedMap; + if (mainIndex) { + // Returns the maximum across all tracked timestamps, including those that + // belong only to dynamic attribute values (TimeMap columns) and not to + // element existence (TimeSet). See getMinTimestamp() for details. + if (!sortedMap.isEmpty()) { + return sortedMap.lastDoubleKey(); + } + } else { + if (!sortedMap.isEmpty()) { + ObjectBidirectionalIterator bi = sortedMap.double2IntEntrySet() + .iterator(sortedMap.double2IntEntrySet().last()); + while (bi.hasPrevious()) { + Double2IntMap.Entry entry = bi.previous(); + double timestamp = entry.getDoubleKey(); + int index = entry.getIntValue(); + if (index < timestamps.length) { + TimeIndexEntry timestampEntry = timestamps[index]; + if (timestampEntry != null) { + return timestamp; + } + } + } + } + } + return Double.POSITIVE_INFINITY; + } finally { + unlock(); + } + } + + @Override + public ElementIterable get(double timestamp) { + checkDouble(timestamp); + + lock(); + try { + Integer index = timestampIndexStore.timeSortedMap.get(timestamp); + if (index != null && index < timestamps.length) { + TimeIndexEntry ts = timestamps[index]; + if (ts != null) { + return new ElementSetWrapperIterable(new ObjectOpenHashSet<>(ts.elementSet)); + } + } + return ElementIterable.EMPTY; + } finally { + unlock(); + } + } + + @Override + public ElementIterable get(Interval interval) { + checkDouble(interval.getLow()); + checkDouble(interval.getHigh()); + + lock(); + try { + ObjectSet elements = new ObjectOpenHashSet<>(); + Double2IntSortedMap sortedMap = (Double2IntSortedMap) timestampIndexStore.timeSortedMap; + if (!sortedMap.isEmpty()) { + for (Double2IntMap.Entry entry : sortedMap.tailMap(interval.getLow()).double2IntEntrySet()) { + double timestamp = entry.getDoubleKey(); + int index = entry.getIntValue(); + if (timestamp <= interval.getHigh()) { + if (index < timestamps.length) { + TimeIndexEntry ts = timestamps[index]; + if (ts != null) { + elements.addAll(ts.elementSet); + } + } + } else { + break; + } + } + } + if (!elements.isEmpty()) { + return new ElementSetWrapperIterable(elements); + } + return ElementIterable.EMPTY; + } finally { + unlock(); + } + } +} diff --git a/store/src/main/java/org/gephi/graph/impl/TimestampIndexStore.java b/src/main/java/org/gephi/graph/impl/TimestampIndexStore.java similarity index 95% rename from store/src/main/java/org/gephi/graph/impl/TimestampIndexStore.java rename to src/main/java/org/gephi/graph/impl/TimestampIndexStore.java index 6116150e..f9af6929 100644 --- a/store/src/main/java/org/gephi/graph/impl/TimestampIndexStore.java +++ b/src/main/java/org/gephi/graph/impl/TimestampIndexStore.java @@ -16,13 +16,13 @@ package org.gephi.graph.impl; import it.unimi.dsi.fastutil.doubles.Double2IntRBTreeMap; -import org.gephi.graph.api.types.TimestampSet; import org.gephi.graph.api.Element; import org.gephi.graph.api.types.TimestampMap; +import org.gephi.graph.api.types.TimestampSet; public class TimestampIndexStore extends TimeIndexStore> { - public TimestampIndexStore(Class type, GraphLock lock, boolean indexed) { + public TimestampIndexStore(Class type, TableLockImpl lock, boolean indexed) { super(type, lock, indexed, new Double2IntRBTreeMap()); mainIndex = indexed ? new TimestampIndexImpl(this, true) : null; } diff --git a/store/src/main/java/org/gephi/graph/impl/TimestampsParser.java b/src/main/java/org/gephi/graph/impl/TimestampsParser.java similarity index 79% rename from store/src/main/java/org/gephi/graph/impl/TimestampsParser.java rename to src/main/java/org/gephi/graph/impl/TimestampsParser.java index c21e0605..2362304c 100644 --- a/store/src/main/java/org/gephi/graph/impl/TimestampsParser.java +++ b/src/main/java/org/gephi/graph/impl/TimestampsParser.java @@ -15,17 +15,21 @@ */ package org.gephi.graph.impl; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import org.gephi.graph.api.AttributeUtils; import static org.gephi.graph.impl.FormattingAndParsingUtils.COMMA; import static org.gephi.graph.impl.FormattingAndParsingUtils.DYNAMIC_TYPE_LEFT_BOUND; import static org.gephi.graph.impl.FormattingAndParsingUtils.DYNAMIC_TYPE_RIGHT_BOUND; +import static org.gephi.graph.impl.FormattingAndParsingUtils.EMPTY_VALUE; import static org.gephi.graph.impl.FormattingAndParsingUtils.LEFT_BOUND_BRACKET; import static org.gephi.graph.impl.FormattingAndParsingUtils.LEFT_BOUND_SQUARE_BRACKET; import static org.gephi.graph.impl.FormattingAndParsingUtils.RIGHT_BOUND_BRACKET; import static org.gephi.graph.impl.FormattingAndParsingUtils.RIGHT_BOUND_SQUARE_BRACKET; + +import java.io.IOException; +import java.io.StringReader; +import java.time.ZoneId; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.types.TimestampBooleanMap; import org.gephi.graph.api.types.TimestampByteMap; import org.gephi.graph.api.types.TimestampCharMap; @@ -37,8 +41,6 @@ import org.gephi.graph.api.types.TimestampSet; import org.gephi.graph.api.types.TimestampShortMap; import org.gephi.graph.api.types.TimestampStringMap; -import org.joda.time.DateTimeZone; -import static org.gephi.graph.impl.FormattingAndParsingUtils.EMPTY_VALUE; /** *

@@ -46,18 +48,16 @@ *

* *

- * The standard format for {@link TimestampMap} is <[timestamp, value1]; - * [timestamp, value2]>. + * The standard format for {@link TimestampMap} is <[timestamp, value1]; [timestamp, value2]>. *

* *

- * The standard format for {@link TimestampSet} is <[timestamp1, timestamp2, - * timestamp3, ...]>. + * The standard format for {@link TimestampSet} is <[timestamp1, timestamp2, timestamp3, ...]>. *

* *

- * Timestamps values can be both numbers and ISO dates or datetimes. Dates and - * datetimes will be converted to their millisecond-precision timestamp. + * Timestamps values can be both numbers and ISO dates or datetimes. Dates and datetimes will be converted to their + * millisecond-precision timestamp. *

* * Examples of valid timestamp maps are: @@ -75,9 +75,8 @@ * * *

- * The most correct examples are those that include < > and proper commas - * and semicolons for separation, but the parser will be indulgent when - * possible. + * The most correct examples are those that include < > and proper commas and semicolons for separation, but the + * parser will be indulgent when possible. *

* * @author Eduardo Ramos @@ -88,14 +87,12 @@ public final class TimestampsParser { * Parses a {@link TimestampSet} type with one or more timestamps. * * @param input Input string to parse - * @param timeZone Time zone to use or null to use default time zone (UTC) - * @return Resulting {@link TimestampSet}, or null if the input equals - * '<empty>' or is null - * @throws IllegalArgumentException Thrown if there are no timestamps in the - * input string or bounds cannot be parsed into doubles or - * dates/datetimes. + * @param zoneId Time zone to use or null to use default time zone (UTC) + * @return Resulting {@link TimestampSet}, or null if the input equals '<empty>' or is null + * @throws IllegalArgumentException Thrown if there are no timestamps in the input string or bounds cannot be parsed + * into doubles or dates/datetimes. */ - public static TimestampSet parseTimestampSet(String input, DateTimeZone timeZone) throws IllegalArgumentException { + public static TimestampSet parseTimestampSet(String input, ZoneId zoneId) throws IllegalArgumentException { if (input == null) { return null; } @@ -151,45 +148,42 @@ public static TimestampSet parseTimestampSet(String input, DateTimeZone timeZone TimestampSet result = new TimestampSet(values.size()); - for (String value : values) { - result.add(FormattingAndParsingUtils.parseDateTimeOrTimestamp(value, timeZone)); + try { + for (String value : values) { + result.add(FormattingAndParsingUtils.parseDateTimeOrTimestamp(value, zoneId)); + } + } catch (DateTimeParseException ex) { + throw new IllegalArgumentException("Invalid timestamp value: " + ex.getMessage(), ex); } return result; } /** - * Parses a {@link TimestampSet} type with one or more timestamps. Default - * time zone is used (UTC). + * Parses a {@link TimestampSet} type with one or more timestamps. Default time zone is used (UTC). * * @param input Input string to parse - * @return Resulting {@link TimestampSet}, or null if the input equals - * '<empty>' or is null - * @throws IllegalArgumentException Thrown if there are no timestamps in the - * input string or bounds cannot be parsed into doubles or - * dates/datetimes. + * @return Resulting {@link TimestampSet}, or null if the input equals '<empty>' or is null + * @throws IllegalArgumentException Thrown if there are no timestamps in the input string or bounds cannot be parsed + * into doubles or dates/datetimes. */ public static TimestampSet parseTimestampSet(String input) throws IllegalArgumentException { return parseTimestampSet(input, null); } /** - * Parses a {@link TimestampMap} type with one or more timestamps, and their - * associated values. + * Parses a {@link TimestampMap} type with one or more timestamps, and their associated values. * * @param Underlying type of the {@link TimestampMap} values - * @param typeClass Simple type or {@link TimestampMap} subtype for the - * result values. + * @param typeClass Simple type or {@link TimestampMap} subtype for the result values. * @param input Input string to parse - * @param timeZone Time zone to use or null to use default time zone (UTC) - * @return Resulting {@link TimestampMap}, or null if the input equals - * '<empty>' or is null - * @throws IllegalArgumentException Thrown if type class is not supported, - * any of the timestamps don't have a value or have an invalid - * value, there are no timestamps in the input string or bounds - * cannot be parsed into doubles or dates/datetimes. + * @param zoneId Time zone to use or null to use default time zone (UTC) + * @return Resulting {@link TimestampMap}, or null if the input equals '<empty>' or is null + * @throws IllegalArgumentException Thrown if type class is not supported, any of the timestamps don't have a value + * or have an invalid value, there are no timestamps in the input string or bounds cannot be parsed into + * doubles or dates/datetimes. */ - public static TimestampMap parseTimestampMap(Class typeClass, String input, DateTimeZone timeZone) throws IllegalArgumentException { + public static TimestampMap parseTimestampMap(Class typeClass, String input, ZoneId zoneId) throws IllegalArgumentException { if (typeClass == null) { throw new IllegalArgumentException("typeClass required"); } @@ -220,7 +214,7 @@ public static TimestampMap parseTimestampMap(Class typeClass, String i } else if (typeClass.equals(Character.class)) { result = new TimestampCharMap(); } else { - throw new IllegalArgumentException("Unsupported type " + typeClass.getClass().getCanonicalName()); + throw new IllegalArgumentException("Unsupported type " + typeClass.getCanonicalName()); } if (input.equalsIgnoreCase(EMPTY_VALUE)) { @@ -243,7 +237,7 @@ public static TimestampMap parseTimestampMap(Class typeClass, String i switch (c) { case LEFT_BOUND_SQUARE_BRACKET: case LEFT_BOUND_BRACKET: - parseTimestampAndValue(typeClass, reader, result, timeZone); + parseTimestampAndValue(typeClass, reader, result, zoneId); break; default: // Ignore other chars outside of bounds @@ -257,25 +251,22 @@ public static TimestampMap parseTimestampMap(Class typeClass, String i } /** - * Parses a {@link TimestampMap} type with one or more timestamps, and their - * associated values. Default time zone is used (UTC). + * Parses a {@link TimestampMap} type with one or more timestamps, and their associated values. Default time zone is + * used (UTC). * * @param Underlying type of the {@link TimestampMap} values - * @param typeClass Simple type or {@link TimestampMap} subtype for the - * result values. + * @param typeClass Simple type or {@link TimestampMap} subtype for the result values. * @param input Input string to parse - * @return Resulting {@link TimestampMap}, or null if the input equals - * '<empty>' or is null - * @throws IllegalArgumentException Thrown if type class is not supported, - * any of the timestamps don't have a value or have an invalid - * value, there are no timestamps in the input string or bounds - * cannot be parsed into doubles or dates/datetimes. + * @return Resulting {@link TimestampMap}, or null if the input equals '<empty>' or is null + * @throws IllegalArgumentException Thrown if type class is not supported, any of the timestamps don't have a value + * or have an invalid value, there are no timestamps in the input string or bounds cannot be parsed into + * doubles or dates/datetimes. */ public static TimestampMap parseTimestampMap(Class typeClass, String input) throws IllegalArgumentException { return parseTimestampMap(typeClass, input, null); } - private static void parseTimestampAndValue(Class typeClass, StringReader reader, TimestampMap result, DateTimeZone timeZone) throws IOException { + private static void parseTimestampAndValue(Class typeClass, StringReader reader, TimestampMap result, ZoneId zoneId) throws IOException { ArrayList values = new ArrayList<>(); int r; @@ -285,7 +276,7 @@ private static void parseTimestampAndValue(Class typeClass, StringReader switch (c) { case RIGHT_BOUND_SQUARE_BRACKET: case RIGHT_BOUND_BRACKET: - addTimestampAndValue(typeClass, values, result, timeZone); + addTimestampAndValue(typeClass, values, result, zoneId); return; case ' ': case '\t': @@ -306,19 +297,23 @@ private static void parseTimestampAndValue(Class typeClass, StringReader } } - addTimestampAndValue(typeClass, values, result, timeZone); + addTimestampAndValue(typeClass, values, result, zoneId); } - private static void addTimestampAndValue(Class typeClass, ArrayList values, TimestampMap result, DateTimeZone timeZone) { + private static void addTimestampAndValue(Class typeClass, ArrayList values, TimestampMap result, ZoneId zoneId) { if (values.size() != 2) { throw new IllegalArgumentException("Each timestamp and value array must have 2 values"); } - double timestamp = FormattingAndParsingUtils.parseDateTimeOrTimestamp(values.get(0), timeZone); + try { + double timestamp = FormattingAndParsingUtils.parseDateTimeOrTimestamp(values.get(0), zoneId); - String valString = values.get(1); - T value = FormattingAndParsingUtils.convertValue(typeClass, valString); + String valString = values.get(1); + T value = FormattingAndParsingUtils.convertValue(typeClass, valString); - result.put(timestamp, value); + result.put(timestamp, value); + } catch (DateTimeParseException ex) { + throw new IllegalArgumentException("Invalid timestamp value: " + values.get(0), ex); + } } } diff --git a/store/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java b/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java similarity index 81% rename from store/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java rename to src/main/java/org/gephi/graph/impl/UndirectedDecorator.java index 82ee5f16..013d15ad 100644 --- a/store/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java +++ b/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java @@ -25,7 +25,7 @@ import org.gephi.graph.api.Interval; import org.gephi.graph.api.Node; import org.gephi.graph.api.NodeIterable; -import org.gephi.graph.api.SpatialContext; +import org.gephi.graph.api.SpatialIndex; import org.gephi.graph.api.Subgraph; import org.gephi.graph.api.UndirectedGraph; import org.gephi.graph.api.UndirectedSubgraph; @@ -86,6 +86,16 @@ public boolean removeAllNodes(Collection nodes) { return store.removeAllNodes(nodes); } + @Override + public boolean retainNodes(Collection nodes) { + return store.retainNodes(nodes); + } + + @Override + public boolean retainEdges(Collection edges) { + return store.retainEdges(edges); + } + @Override public boolean contains(Node node) { return store.contains(node); @@ -101,6 +111,11 @@ public Node getNode(Object id) { return store.getNode(id); } + @Override + public Node getNodeByStoreId(int id) { + return store.getNodeByStoreId(id); + } + @Override public boolean hasNode(final Object id) { return store.hasNode(id); @@ -111,6 +126,11 @@ public Edge getEdge(Object id) { return store.getEdge(id); } + @Override + public Edge getEdgeByStoreId(int storeId) { + return store.getEdgeByStoreId(storeId); + } + @Override public boolean hasEdge(final Object id) { return store.hasEdge(id); @@ -128,7 +148,8 @@ public Edge getEdge(Node node1, Node node2) { @Override public EdgeIterable getEdges(Node node1, Node node2) { - return store.getEdgeIterableWrapper(store.edgeStore.edgesUndirectedIterator(node1, node2)); + return new EdgeIterableWrapper(() -> store.edgeStore.edgesUndirectedIterator(node1, node2), + store.getAutoLock()); } @Override @@ -143,7 +164,8 @@ public Edge getEdge(Node node1, Node node2, int type) { @Override public EdgeIterable getEdges(Node node1, Node node2, int type) { - return store.getEdgeIterableWrapper(store.edgeStore.edgesUndirectedIterator(node1, node2, type)); + return new EdgeIterableWrapper(() -> store.edgeStore.edgesUndirectedIterator(node1, node2, type), + store.getAutoLock()); } @Override @@ -153,32 +175,40 @@ public NodeIterable getNodes() { @Override public EdgeIterable getEdges() { - return store.getEdgeIterableWrapper(store.edgeStore.iteratorUndirected()); + return new EdgeIterableWrapper(store.edgeStore::iteratorUndirected, store.edgeStore::spliteratorUndirected, + store.getAutoLock()); + } + + @Override + public EdgeIterable getEdges(int type) { + return new EdgeIterableWrapper(() -> store.edgeStore.iteratorType(type, true), + () -> store.edgeStore.spliteratorType(type, true), store.getAutoLock()); } @Override public EdgeIterable getSelfLoops() { - return store.getEdgeIterableWrapper(store.edgeStore.iteratorSelfLoop()); + return new EdgeIterableWrapper(store.edgeStore::iteratorSelfLoop, store.edgeStore::spliteratorSelfLoop, + store.getAutoLock()); } @Override public NodeIterable getNeighbors(Node node) { - return store.getNodeIterableWrapper(store.edgeStore.neighborIterator(node)); + return new NodeIterableWrapper(() -> store.edgeStore.neighborIterator(node), store.getAutoLock()); } @Override public NodeIterable getNeighbors(Node node, int type) { - return store.getNodeIterableWrapper(store.edgeStore.neighborIterator(node, type)); + return new NodeIterableWrapper(() -> store.edgeStore.neighborIterator(node, type), store.getAutoLock()); } @Override public EdgeIterable getEdges(Node node) { - return store.getEdgeIterableWrapper(store.edgeStore.edgeUndirectedIterator(node)); + return new EdgeIterableWrapper(() -> store.edgeStore.edgeUndirectedIterator(node, true), store.getAutoLock()); } @Override public EdgeIterable getEdges(Node node, int type) { - return store.getEdgeIterableWrapper(store.edgeStore.edgeUndirectedIterator(node, type)); + return new EdgeIterableWrapper(() -> store.edgeStore.edgeUndirectedIterator(node, type), store.getAutoLock()); } @Override @@ -344,11 +374,21 @@ public void writeUnlock() { store.autoWriteUnlock(); } + @Override + public GraphLockImpl getLock() { + return store.getLock(); + } + @Override public GraphModel getModel() { return store.graphModel; } + @Override + public int getVersion() { + return store.getVersion(); + } + @Override public boolean isDirected() { return false; @@ -390,7 +430,7 @@ public Graph getRootGraph() { } @Override - public SpatialContext getSpatialContext() { - return store.getSpatialContext(); + public SpatialIndex getSpatialIndex() { + return store.getSpatialIndex(); } } diff --git a/store/src/main/java/org/gephi/graph/impl/utils/DataInputOutput.java b/src/main/java/org/gephi/graph/impl/utils/DataInputOutput.java similarity index 99% rename from store/src/main/java/org/gephi/graph/impl/utils/DataInputOutput.java rename to src/main/java/org/gephi/graph/impl/utils/DataInputOutput.java index e782fde6..a54bf111 100644 --- a/store/src/main/java/org/gephi/graph/impl/utils/DataInputOutput.java +++ b/src/main/java/org/gephi/graph/impl/utils/DataInputOutput.java @@ -124,7 +124,7 @@ public int readUnsignedShort() throws IOException { @Override public char readChar() throws IOException { - return (char) readInt(); + return (char) readUnsignedShort(); } @Override @@ -209,7 +209,7 @@ public void writeShort(int v) throws IOException { @Override public void writeChar(int v) throws IOException { - writeInt(v); + writeShort(v); } @Override diff --git a/store/src/main/java/org/gephi/graph/impl/utils/LongPacker.java b/src/main/java/org/gephi/graph/impl/utils/LongPacker.java similarity index 95% rename from store/src/main/java/org/gephi/graph/impl/utils/LongPacker.java rename to src/main/java/org/gephi/graph/impl/utils/LongPacker.java index 9dab4a4b..0d1a7b92 100644 --- a/store/src/main/java/org/gephi/graph/impl/utils/LongPacker.java +++ b/src/main/java/org/gephi/graph/impl/utils/LongPacker.java @@ -21,11 +21,9 @@ import java.nio.ByteBuffer; /** - * Packing utility for non-negative long and int - * values. + * Packing utility for non-negative long and int values. *

- * Originally developed for Kryo by Nathan Sweet. Modified for JDBM by Jan - * Kotek. + * Originally developed for Kryo by Nathan Sweet. Modified for JDBM by Jan Kotek. */ public final class LongPacker { @@ -35,8 +33,8 @@ private LongPacker() { } /** - * Pack non-negative long into output stream. It will occupy 1-10 bytes - * depending on value (lower values occupy smaller space) + * Pack non-negative long into output stream. It will occupy 1-10 bytes depending on value (lower values occupy + * smaller space) * * @param os the data output * @param value the long value @@ -60,8 +58,8 @@ static public int packLong(DataOutput os, long value) throws IOException { } /** - * Pack non-negative long into byte array. It will occupy 1-10 bytes - * depending on value (lower values occupy smaller space) + * Pack non-negative long into byte array. It will occupy 1-10 bytes depending on value (lower values occupy smaller + * space) * * @param ba the byte array * @param value the long value @@ -136,8 +134,8 @@ static public long unpackLong(byte[] ba, int index) { } /** - * Pack non-negative int into output stream. It will occupy 1-5 bytes - * depending on value (lower values occupy smaller space) + * Pack non-negative int into output stream. It will occupy 1-5 bytes depending on value (lower values occupy + * smaller space) * * @param os the data output * @param value the value diff --git a/store/src/main/java/org/gephi/graph/impl/utils/MapDeepEquals.java b/src/main/java/org/gephi/graph/impl/utils/MapDeepEquals.java similarity index 92% rename from store/src/main/java/org/gephi/graph/impl/utils/MapDeepEquals.java rename to src/main/java/org/gephi/graph/impl/utils/MapDeepEquals.java index 182fd2ce..7c1e8120 100644 --- a/store/src/main/java/org/gephi/graph/impl/utils/MapDeepEquals.java +++ b/src/main/java/org/gephi/graph/impl/utils/MapDeepEquals.java @@ -27,9 +27,8 @@ private MapDeepEquals() { } /** - * Compares two maps for equality. This is based around the idea that if the - * keys are deep equal and the values the keys return are deep equal then - * the maps are equal. + * Compares two maps for equality. This is based around the idea that if the keys are deep equal and the values the + * keys return are deep equal then the maps are equal. * * @param m1 - first map * @param m2 - second map diff --git a/store/src/main/java/org/gephi/graph/spi/LayoutData.java b/src/main/java/org/gephi/graph/spi/LayoutData.java similarity index 85% rename from store/src/main/java/org/gephi/graph/spi/LayoutData.java rename to src/main/java/org/gephi/graph/spi/LayoutData.java index 0685ec14..32c11bce 100644 --- a/store/src/main/java/org/gephi/graph/spi/LayoutData.java +++ b/src/main/java/org/gephi/graph/spi/LayoutData.java @@ -18,13 +18,10 @@ import org.gephi.graph.api.Node; /** - * Interface for node metadata to handle custom layout attributes more - * efficiently. + * Interface for node metadata to handle custom layout attributes more efficiently. *

* Layout implementations can implement this interface and use the - * {@link Node#setLayoutData(org.gephi.graph.spi.LayoutData) - * } method to - * associate any metadata with the node. + * {@link Node#setLayoutData(org.gephi.graph.spi.LayoutData) } method to associate any metadata with the node. */ public interface LayoutData { } diff --git a/src/main/java/org/gephi/graph/spi/package.html b/src/main/java/org/gephi/graph/spi/package.html new file mode 100644 index 00000000..78522c3c --- /dev/null +++ b/src/main/java/org/gephi/graph/spi/package.html @@ -0,0 +1,4 @@ + + + SPI interfaces clients can implement to extend the API. + diff --git a/store/src/test/java/org/gephi/graph/impl/AttributeUtilsTest.java b/src/test/java/org/gephi/graph/api/AttributeUtilsTest.java similarity index 74% rename from store/src/test/java/org/gephi/graph/impl/AttributeUtilsTest.java rename to src/test/java/org/gephi/graph/api/AttributeUtilsTest.java index 18f95ca6..d1194ce5 100644 --- a/store/src/test/java/org/gephi/graph/impl/AttributeUtilsTest.java +++ b/src/test/java/org/gephi/graph/api/AttributeUtilsTest.java @@ -13,11 +13,17 @@ * License for the specific language governing permissions and limitations under * the License. */ -package org.gephi.graph.impl; +package org.gephi.graph.api; import java.awt.Color; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -55,7 +61,7 @@ import org.gephi.graph.api.types.IntervalShortMap; import org.gephi.graph.api.types.IntervalStringMap; import org.gephi.graph.api.types.TimestampSet; -import org.joda.time.DateTimeZone; +import org.gephi.graph.impl.TableImpl; import org.testng.Assert; import org.testng.annotations.Test; @@ -74,7 +80,9 @@ public void testParseSimpleTypes() { Assert.assertEquals(AttributeUtils.parse("foo", String.class), "foo"); Assert.assertEquals(AttributeUtils.parse("0", Integer.class), 0); Assert.assertEquals(AttributeUtils.parse("0", Float.class), 0f); + Assert.assertEquals(AttributeUtils.parse("0.5", Float.class), 0.5f); Assert.assertEquals(AttributeUtils.parse("0", Double.class), 0.0); + Assert.assertEquals(AttributeUtils.parse("0.5", Double.class), 0.5); Assert.assertEquals(AttributeUtils.parse("0", Long.class), 0l); Assert.assertEquals(AttributeUtils.parse("0", Short.class), (short) 0); Assert.assertEquals(AttributeUtils.parse("0", Byte.class), (byte) 0); @@ -82,18 +90,21 @@ public void testParseSimpleTypes() { Assert.assertEquals(AttributeUtils.parse("true", Boolean.class), true); Assert.assertEquals(AttributeUtils.parse("1", Boolean.class), true); Assert.assertEquals(AttributeUtils.parse("0", Boolean.class), false); - Assert.assertEquals(AttributeUtils.parse("123456789123456789123456789123456789", BigInteger.class), new BigInteger( - "123456789123456789123456789123456789")); + Assert.assertEquals(AttributeUtils + .parse("123456789123456789123456789123456789", BigInteger.class), new BigInteger( + "123456789123456789123456789123456789")); Assert.assertEquals(AttributeUtils .parse("123456789123456789123456789123456789.123456789123456789123456789123456789", BigDecimal.class), new BigDecimal( - "123456789123456789123456789123456789.123456789123456789123456789123456789")); + "123456789123456789123456789123456789.123456789123456789123456789123456789")); } @Test public void testParsePrimitiveTypes() { Assert.assertEquals(AttributeUtils.parse("0", int.class), 0); Assert.assertEquals(AttributeUtils.parse("0", float.class), 0f); + Assert.assertEquals(AttributeUtils.parse("0.5", float.class), 0.5f); Assert.assertEquals(AttributeUtils.parse("0", double.class), 0.0); + Assert.assertEquals(AttributeUtils.parse("0.5", double.class), 0.5); Assert.assertEquals(AttributeUtils.parse("0", long.class), 0l); Assert.assertEquals(AttributeUtils.parse("0", short.class), (short) 0); Assert.assertEquals(AttributeUtils.parse("0", byte.class), (byte) 0); @@ -103,10 +114,24 @@ public void testParsePrimitiveTypes() { Assert.assertEquals(AttributeUtils.parse("0", boolean.class), false); } + @Test + public void testParseInstant() { + Assert.assertEquals(AttributeUtils.parse("2014-01-01T00:00:00Z", Instant.class), Instant + .parse("2014-01-01T00:00:00Z")); + Assert.assertEquals(AttributeUtils.parse("2014-01-01", Instant.class), Instant.parse("2014-01-01T00:00:00Z")); + } + + @Test(expectedExceptions = DateTimeParseException.class) + public void testParseInstantException() { + AttributeUtils.parse("foo", Instant.class); + } + @Test public void testParseArrayTypes() { - Assert.assertEquals(AttributeUtils.parse("[true, false, 1, 0, null]", Boolean[].class), new Boolean[] { true, false, true, false, null }); - Assert.assertEquals(AttributeUtils.parse("[true, false, 1, 0]", boolean[].class), new boolean[] { true, false, true, false }); + Assert.assertEquals(AttributeUtils + .parse("[true, false, 1, 0, null]", Boolean[].class), new Boolean[] { true, false, true, false, null }); + Assert.assertEquals(AttributeUtils + .parse("[true, false, 1, 0]", boolean[].class), new boolean[] { true, false, true, false }); Assert.assertEquals(AttributeUtils.parse("[-1, 3, null]", Integer[].class), new Integer[] { -1, 3, null }); Assert.assertEquals(AttributeUtils.parse("[-1, 3, null]", Integer[].class).getClass(), Integer[].class); @@ -121,20 +146,28 @@ public void testParseArrayTypes() { Assert.assertEquals(AttributeUtils.parse("[-1, 3, null]", Long[].class), new Long[] { -1l, 3l, null }); Assert.assertEquals(AttributeUtils.parse("[-1, 0, 2]", long[].class), new long[] { -1, 0, 2 }); - Assert.assertEquals(AttributeUtils.parse("[-1e6, 1, .001, 2000000., null]", Float[].class), new Float[] { -1e6f, 1.0f, .001f, 2e6f, null }); + Assert.assertEquals(AttributeUtils + .parse("[-1e6, 1, .001, 2000000., null]", Float[].class), new Float[] { -1e6f, 1.0f, .001f, 2e6f, null }); Assert.assertEquals(AttributeUtils.parse("[1]", float[].class).getClass(), float[].class); - Assert.assertEquals(AttributeUtils.parse("[-1e6, 1, .001, 2e6]", float[].class), new float[] { -1e6f, 1.0f, .001f, 2e6f }); + Assert.assertEquals(AttributeUtils + .parse("[-1e6, 1, .001, 2e6]", float[].class), new float[] { -1e6f, 1.0f, .001f, 2e6f }); - Assert.assertEquals(AttributeUtils.parse("[-1e6, 1, .001, 2000000., null]", Double[].class), new Double[] { -1e6, 1.0, .001, 2e6, null }); - Assert.assertEquals(AttributeUtils.parse("[-1e6, 1, .001, 2e6]", double[].class), new double[] { -1e6, 1.0, .001, 2e6 }); + Assert.assertEquals(AttributeUtils + .parse("[-1e6, 1, .001, 2000000., null]", Double[].class), new Double[] { -1e6, 1.0, .001, 2e6, null }); + Assert.assertEquals(AttributeUtils + .parse("[-1e6, 1, .001, 2e6]", double[].class), new double[] { -1e6, 1.0, .001, 2e6 }); Assert.assertEquals(AttributeUtils.parse("[-1e6, 1, .001, 2e6]", double[].class).getClass(), double[].class); - Assert.assertEquals(AttributeUtils.parse("[' true ', 'null', null]", String[].class), new String[] { " true ", "null", null }); - Assert.assertEquals(AttributeUtils.parse("['123456789123456789123456789123456789']", BigInteger[].class), new BigInteger[] { new BigInteger( - "123456789123456789123456789123456789") }); + Assert.assertEquals(AttributeUtils + .parse("[' true ', 'null', null]", String[].class), new String[] { " true ", "null", null }); + Assert.assertEquals(AttributeUtils.parse("['null']", Integer[].class), new Integer[] { null }); + Assert.assertEquals(AttributeUtils.parse("['null']", Double[].class), new Double[] { null }); + Assert.assertEquals(AttributeUtils + .parse("['123456789123456789123456789123456789']", BigInteger[].class), new BigInteger[] { new BigInteger( + "123456789123456789123456789123456789") }); Assert.assertEquals(AttributeUtils .parse("['123456789123456789123456789123456789.123456789123456789123456789123456789']", BigDecimal[].class), new BigDecimal[] { new BigDecimal( - "123456789123456789123456789123456789.123456789123456789123456789123456789") }); + "123456789123456789123456789123456789.123456789123456789123456789123456789") }); } @Test @@ -233,40 +266,47 @@ public void testParseDynamicTimestampTypesWithTimeZone() { Assert.assertEquals(AttributeUtils.parse("<[2015-01-01T00:00:00]>", TimestampSet.class), AttributeUtils .parse("<[2015-01-01T00:00:00]>", TimestampSet.class, null)); Assert.assertEquals(AttributeUtils.parse("<[2015-01-01T00:00:00]>", TimestampSet.class), AttributeUtils - .parse("<[2015-01-01T00:00:00]>", TimestampSet.class, DateTimeZone.UTC)); + .parse("<[2015-01-01T00:00:00]>", TimestampSet.class, ZoneId.of("UTC"))); Assert.assertEquals(AttributeUtils.parse("<[2015-01-01T00:00:00]>", TimestampSet.class), AttributeUtils - .parse("<[2015-01-01T01:30:00]>", TimestampSet.class, DateTimeZone.forID("+01:30"))); + .parse("<[2015-01-01T01:30:00]>", TimestampSet.class, ZoneId.of("+01:30"))); // Maps - Assert.assertEquals(AttributeUtils.parse("<[2015-01-01T00:00:00, val]>", TimestampStringMap.class), AttributeUtils - .parse("<[2015-01-01T00:00:00, val]>", TimestampStringMap.class, null)); - Assert.assertEquals(AttributeUtils.parse("<[2015-01-01T00:00:00, val]>", TimestampStringMap.class), AttributeUtils - .parse("<[2015-01-01T00:00:00, val]>", TimestampStringMap.class, DateTimeZone.UTC)); - Assert.assertEquals(AttributeUtils.parse("<[2015-01-01T00:00:00, val]>", TimestampStringMap.class), AttributeUtils - .parse("<[2015-01-01T01:30:00, val]>", TimestampStringMap.class, DateTimeZone.forID("+01:30"))); + Assert.assertEquals(AttributeUtils + .parse("<[2015-01-01T00:00:00, val]>", TimestampStringMap.class), AttributeUtils + .parse("<[2015-01-01T00:00:00, val]>", TimestampStringMap.class, null)); + Assert.assertEquals(AttributeUtils + .parse("<[2015-01-01T00:00:00, val]>", TimestampStringMap.class), AttributeUtils + .parse("<[2015-01-01T00:00:00, val]>", TimestampStringMap.class, ZoneId.of("UTC"))); + Assert.assertEquals(AttributeUtils + .parse("<[2015-01-01T00:00:00, val]>", TimestampStringMap.class), AttributeUtils + .parse("<[2015-01-01T01:30:00, val]>", TimestampStringMap.class, ZoneId.of("+01:30"))); } @Test public void testParseDynamicIntervalTypesWithTimeZone() { // Sets - Assert.assertEquals(AttributeUtils.parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00]>", IntervalSet.class), AttributeUtils - .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00]>", IntervalSet.class, null)); - Assert.assertEquals(AttributeUtils.parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00]>", IntervalSet.class), AttributeUtils - .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00]>", IntervalSet.class, DateTimeZone.UTC)); - Assert.assertEquals(AttributeUtils.parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00]>", IntervalSet.class), AttributeUtils - .parse("<[2014-12-31T22:00:00, 2015-01-01T00:00:00]>", IntervalSet.class, DateTimeZone.forID("-02:00"))); + Assert.assertEquals(AttributeUtils + .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00]>", IntervalSet.class), AttributeUtils + .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00]>", IntervalSet.class, null)); + Assert.assertEquals(AttributeUtils + .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00]>", IntervalSet.class), AttributeUtils + .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00]>", IntervalSet.class, ZoneId.of("UTC"))); + Assert.assertEquals(AttributeUtils + .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00]>", IntervalSet.class), AttributeUtils + .parse("<[2014-12-31T22:00:00, 2015-01-01T00:00:00]>", IntervalSet.class, ZoneId.of("-02:00"))); // Maps Assert.assertEquals(AttributeUtils .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00, val]>", IntervalStringMap.class), AttributeUtils - .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00, val]>", IntervalStringMap.class, null)); + .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00, val]>", IntervalStringMap.class, null)); Assert.assertEquals(AttributeUtils .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00, val]>", IntervalStringMap.class), AttributeUtils - .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00, val]>", IntervalStringMap.class, DateTimeZone.UTC)); + .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00, val]>", IntervalStringMap.class, ZoneId + .of("UTC"))); Assert.assertEquals(AttributeUtils .parse("<[2015-01-01T00:00:00, 2015-01-01T02:00:00, val]>", IntervalStringMap.class), AttributeUtils - .parse("<[2014-12-31T22:00:00, 2015-01-01T00:00:00, val]>", IntervalStringMap.class, DateTimeZone - .forID("-02:00"))); + .parse("<[2014-12-31T22:00:00, 2015-01-01T00:00:00, val]>", IntervalStringMap.class, ZoneId + .of("-02:00"))); } @Test(expectedExceptions = IllegalArgumentException.class) @@ -277,6 +317,7 @@ public void testParseCharInvalid() { @Test public void testParseNull() { Assert.assertNull(AttributeUtils.parse(null, Integer.class)); + Assert.assertNull(AttributeUtils.parse("null", Integer.class)); } @Test @@ -284,6 +325,13 @@ public void testParseEmpty() { Assert.assertNull(AttributeUtils.parse("", Integer.class)); } + @Test + public void testParseInfinity() { + Assert.assertEquals(AttributeUtils.parse("Infinity", Double.class), Double.POSITIVE_INFINITY); + Assert.assertEquals(AttributeUtils.parse("+Infinity", Double.class), Double.POSITIVE_INFINITY); + Assert.assertEquals(AttributeUtils.parse("-Infinity", Double.class), Double.NEGATIVE_INFINITY); + } + @Test(expectedExceptions = IllegalArgumentException.class) public void testParseUnsupportedType() { AttributeUtils.parse("test", Color.class); @@ -310,12 +358,14 @@ public void testGetPrimitiveTypeUnsupportedType() { public void testGetPrimitiveArray() { Assert.assertEquals((int[]) AttributeUtils.getPrimitiveArray(new Integer[] { 1, 2 }), new int[] { 1, 2 }); Assert.assertEquals((float[]) AttributeUtils.getPrimitiveArray(new Float[] { 1f, 2f }), new float[] { 1f, 2f }); - Assert.assertEquals((double[]) AttributeUtils.getPrimitiveArray(new Double[] { 1.0, 2.0 }), new double[] { 1.0, 2.0 }); + Assert.assertEquals((double[]) AttributeUtils + .getPrimitiveArray(new Double[] { 1.0, 2.0 }), new double[] { 1.0, 2.0 }); Assert.assertEquals((long[]) AttributeUtils.getPrimitiveArray(new Long[] { 1l, 2l }), new long[] { 1l, 2l }); Assert.assertEquals((char[]) AttributeUtils.getPrimitiveArray(new Character[] { 1, 2 }), new char[] { 1, 2 }); Assert.assertEquals((short[]) AttributeUtils.getPrimitiveArray(new Short[] { 1, 2 }), new short[] { 1, 2 }); Assert.assertEquals((byte[]) AttributeUtils.getPrimitiveArray(new Byte[] { 1, 2 }), new byte[] { 1, 2 }); - Assert.assertEquals((boolean[]) AttributeUtils.getPrimitiveArray(new Boolean[] { true, false }), new boolean[] { true, false }); + Assert.assertEquals((boolean[]) AttributeUtils + .getPrimitiveArray(new Boolean[] { true, false }), new boolean[] { true, false }); } @Test(expectedExceptions = IllegalArgumentException.class) @@ -364,6 +414,7 @@ public void testIsSupported() { Assert.assertTrue(AttributeUtils.isSupported(HashSet.class)); Assert.assertTrue(AttributeUtils.isSupported(Map.class)); Assert.assertTrue(AttributeUtils.isSupported(HashMap.class)); + Assert.assertTrue(AttributeUtils.isSupported(Instant.class)); Assert.assertFalse(AttributeUtils.isSupported(Color.class)); Assert.assertFalse(AttributeUtils.isSupported(Collection.class)); @@ -540,8 +591,8 @@ public void testStandardizeValueMixedMapKeyContent() { public void testParseDate() { Assert.assertEquals(AttributeUtils.parseDateTime("1970-01-01T00:00:00"), 0.0); Assert.assertEquals(AttributeUtils.parseDateTime("1970-01-01T00:00:00", null), 0.0); - Assert.assertEquals(AttributeUtils.parseDateTime("1970-01-01T00:00:00", DateTimeZone.UTC), 0.0); - Assert.assertEquals(AttributeUtils.parseDateTime("1970-01-01T01:30:00", DateTimeZone.forID("+01:30")), 0.0); + Assert.assertEquals(AttributeUtils.parseDateTime("1970-01-01T00:00:00", ZoneId.of("UTC")), 0.0); + Assert.assertEquals(AttributeUtils.parseDateTime("1970-01-01T01:30:00", ZoneId.of("+01:30")), 0.0); Assert.assertEquals(AttributeUtils.parseDateTime("1970-01-01T00:00:00+00:00"), 0.0); Assert.assertEquals(AttributeUtils.parseDateTime("1970-01-01T00:00:00Z"), 0.0); Assert.assertEquals(AttributeUtils.parseDateTime("1970-01-01T00:00:00.000+00:00"), 0.0); @@ -553,9 +604,9 @@ public void testParseDate() { AttributeUtils.parseDateTime("20040401"); Assert.assertEquals(AttributeUtils.parseDateTime("2012-09-12T15:04:01"), AttributeUtils - .parseDateTime("2012-09-12T15:04:01", DateTimeZone.forID("+00:00"))); + .parseDateTime("2012-09-12T15:04:01", ZoneId.of("+00:00"))); Assert.assertEquals(AttributeUtils.parseDateTime("2012-09-12T15:04:01+03:30"), AttributeUtils - .parseDateTime("2012-09-12T15:04:01", DateTimeZone.forID("+03:30"))); + .parseDateTime("2012-09-12T15:04:01", ZoneId.of("+03:30"))); } @Test @@ -573,13 +624,13 @@ public void testParseDateTimeOrTimestamp() { Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("0"), AttributeUtils .parseDateTimeOrTimestamp("1970-01-01T00:00:00Z")); Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("0"), AttributeUtils - .parseDateTimeOrTimestamp("1970-01-01T00:00:00", DateTimeZone.forID("+00:00"))); + .parseDateTimeOrTimestamp("1970-01-01T00:00:00", ZoneId.of("+00:00"))); // Dates Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("1970-01-01T00:00:00"), 0.0); Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("1970-01-01T00:00:00", null), 0.0); - Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("1970-01-01T00:00:00", DateTimeZone.UTC), 0.0); - Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("1970-01-01T01:30:00", DateTimeZone.forID("+01:30")), 0.0); + Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("1970-01-01T00:00:00", ZoneId.of("UTC")), 0.0); + Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("1970-01-01T01:30:00", ZoneId.of("+01:30")), 0.0); Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("1970-01-01T00:00:00+00:00"), 0.0); Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("1970-01-01T00:00:00Z"), 0.0); Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("1970-01-01T00:00:00.000+00:00"), 0.0); @@ -587,9 +638,9 @@ public void testParseDateTimeOrTimestamp() { Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("1969-12-31T22:00:00-02:00"), 0.0); Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("2012-09-12T15:04:01"), AttributeUtils - .parseDateTime("2012-09-12T15:04:01", DateTimeZone.forID("+00:00"))); + .parseDateTime("2012-09-12T15:04:01", ZoneId.of("+00:00"))); Assert.assertEquals(AttributeUtils.parseDateTimeOrTimestamp("2012-09-12T15:04:01+03:30"), AttributeUtils - .parseDateTime("2012-09-12T15:04:01", DateTimeZone.forID("+03:30"))); + .parseDateTime("2012-09-12T15:04:01", ZoneId.of("+03:30"))); } @Test @@ -599,16 +650,29 @@ public void testPrintDate() { Assert.assertEquals(AttributeUtils.printDate(d), date); - Assert.assertEquals(AttributeUtils.printDate(d, DateTimeZone.UTC), date); + Assert.assertEquals(AttributeUtils.printDate(d, ZoneId.of("UTC")), date); Assert.assertEquals(AttributeUtils.printDate(d, null), date); - Assert.assertEquals(AttributeUtils.printDate(d, DateTimeZone.forID("+00:30")), "2003-01-01");// Still - // same - // day - Assert.assertEquals(AttributeUtils.printDate(d, DateTimeZone.forID("+12:00")), "2003-01-01");// Still - // same - // day - Assert.assertEquals(AttributeUtils.printDate(d, DateTimeZone.forID("-00:30")), "2002-12-31");// Previous - // day + Assert.assertEquals(AttributeUtils.printDate(d, ZoneId.of("+00:30")), "2003-01-01");// Still + // same + // day + Assert.assertEquals(AttributeUtils.printDate(d, ZoneId.of("+12:00")), "2003-01-01");// Still + // same + // day + Assert.assertEquals(AttributeUtils.printDate(d, ZoneId.of("-00:30")), "2002-12-31");// Previous + // day + } + + @Test + public void testPrintDateWithInstant() { + String date = "2012-01-05"; + LocalDate localDate = LocalDate.of(2012, 1, 5); + ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.of("UTC")); + + Assert.assertEquals(AttributeUtils.printDate(zonedDateTime.toInstant(), ZoneId.of("UTC")), date); + Assert.assertEquals(AttributeUtils.printDate(zonedDateTime.toInstant(), null), date); + Assert.assertEquals(AttributeUtils.printDate(zonedDateTime.toInstant(), ZoneId.of("+00:30")), date); + Assert.assertEquals(AttributeUtils.printDate(zonedDateTime.toInstant(), ZoneId.of("+12:00")), date); + Assert.assertEquals(AttributeUtils.printDate(zonedDateTime.toInstant(), ZoneId.of("-00:30")), "2012-01-04"); } @Test @@ -619,20 +683,39 @@ public void testPrintDateTime() { String dateInUTC = AttributeUtils.printDateTime(d); Assert.assertEquals(AttributeUtils.parseDateTime(dateInUTC), d); - Assert.assertEquals(AttributeUtils.printDateTime(d, DateTimeZone.UTC), dateInUTC); + Assert.assertEquals(AttributeUtils.printDateTime(d, ZoneId.of("UTC")), dateInUTC); Assert.assertEquals(AttributeUtils.printDateTime(d, null), dateInUTC); Assert.assertEquals(AttributeUtils.printDateTime(d), "2003-01-01T08:00:00.000Z"); - Assert.assertEquals(AttributeUtils.printDateTime(d, DateTimeZone.forID("+00:30")), "2003-01-01T08:30:00.000+00:30"); - Assert.assertEquals(AttributeUtils.printDateTime(d, DateTimeZone.forID("+12:00")), "2003-01-01T20:00:00.000+12:00"); - Assert.assertEquals(AttributeUtils.printDateTime(d, DateTimeZone.forID("-12:00")), "2002-12-31T20:00:00.000-12:00"); + Assert.assertEquals(AttributeUtils.printDateTime(d, ZoneId.of("+00:30")), "2003-01-01T08:30:00.000+00:30"); + Assert.assertEquals(AttributeUtils.printDateTime(d, ZoneId.of("+12:00")), "2003-01-01T20:00:00.000+12:00"); + Assert.assertEquals(AttributeUtils.printDateTime(d, ZoneId.of("-12:00")), "2002-12-31T20:00:00.000-12:00"); + + Assert.assertEquals(AttributeUtils + .printDateTime(AttributeUtils.parseDateTime("2003-01-01T16:00:00", ZoneId.of("+00:00")), ZoneId + .of("+12:00")), "2003-01-02T04:00:00.000+12:00"); + } - Assert.assertEquals(AttributeUtils.printDateTime(AttributeUtils - .parseDateTime("2003-01-01T16:00:00", DateTimeZone.forID("+00:00")), DateTimeZone.forID("+12:00")), "2003-01-02T04:00:00.000+12:00"); + @Test + public void testPrintDateTimeWithInstant() { + String date = "2012-01-05T08:00:00.000Z"; + LocalDate localDate = LocalDate.of(2012, 1, 5); + LocalTime localTime = LocalTime.of(0, 0, 0, 0); + ZonedDateTime zonedDateTime = ZonedDateTime.of(localDate, localTime, ZoneId.of("-08:00")); + + Assert.assertEquals(AttributeUtils.printDateTime(zonedDateTime.toInstant(), ZoneId.of("UTC")), date); + Assert.assertEquals(AttributeUtils.printDateTime(zonedDateTime.toInstant(), null), date); + Assert.assertEquals(AttributeUtils + .printDateTime(zonedDateTime.toInstant(), ZoneId.of("+00:30")), "2012-01-05T08:30:00.000+00:30"); + Assert.assertEquals(AttributeUtils + .printDateTime(zonedDateTime.toInstant(), ZoneId.of("+12:00")), "2012-01-05T20:00:00.000+12:00"); + Assert.assertEquals(AttributeUtils + .printDateTime(zonedDateTime.toInstant(), ZoneId.of("-12:00")), "2012-01-04T20:00:00.000-12:00"); } @Test public void testPrintArray() { - Assert.assertEquals(AttributeUtils.printArray(new String[] { null, "null", " b ", "\"c" }), "[null, \"null\", \" b \", \"\\\"c\"]"); + Assert.assertEquals(AttributeUtils + .printArray(new String[] { null, "null", " b ", "\"c" }), "[null, \"null\", \" b \", \"\\\"c\"]"); Assert.assertEquals(AttributeUtils.printArray(new Integer[] { -1, 2, 3, null }), "[-1, 2, 3, null]"); Assert.assertEquals(AttributeUtils.printArray(new int[] { -1, 2, 3 }), "[-1, 2, 3]"); Assert.assertEquals(AttributeUtils.printArray(new boolean[] { true, false, true }), "[true, false, true]"); @@ -655,16 +738,24 @@ public void testPrint() { Assert.assertEquals(AttributeUtils.print(ts), ts.toString(TimeFormat.DOUBLE)); Assert.assertEquals(AttributeUtils.print(ts, TimeFormat.DATE, null), ts.toString(TimeFormat.DATE, null)); - Assert.assertEquals(AttributeUtils.print(ts, TimeFormat.DATETIME, DateTimeZone.forID("+00:30")), ts - .toString(TimeFormat.DATETIME, DateTimeZone.forID("+00:30"))); + Assert.assertEquals(AttributeUtils.print(ts, TimeFormat.DATETIME, ZoneId.of("+00:30")), ts + .toString(TimeFormat.DATETIME, ZoneId.of("+00:30"))); TimestampIntegerMap tm = new TimestampIntegerMap(); tm.put(d, 42); Assert.assertEquals(AttributeUtils.print(tm), tm.toString(TimeFormat.DOUBLE)); Assert.assertEquals(AttributeUtils.print(tm, TimeFormat.DATE, null), tm.toString(TimeFormat.DATE, null)); - Assert.assertEquals(AttributeUtils.print(tm, TimeFormat.DATETIME, DateTimeZone.forID("+00:30")), tm - .toString(TimeFormat.DATETIME, DateTimeZone.forID("+00:30"))); + Assert.assertEquals(AttributeUtils.print(tm, TimeFormat.DATETIME, ZoneId.of("+00:30")), tm + .toString(TimeFormat.DATETIME, ZoneId.of("+00:30"))); + + Assert.assertEquals(AttributeUtils.print(Instant.ofEpochMilli(0)), "1970-01-01T00:00:00Z"); + } + + @Test + public void testPrintInfinity() { + Assert.assertEquals(AttributeUtils.print(Double.POSITIVE_INFINITY), "Infinity"); + Assert.assertEquals(AttributeUtils.print(Double.NEGATIVE_INFINITY), "-Infinity"); } @Test @@ -799,8 +890,8 @@ public void testgetTypeNameUnsupportedType() { @Test public void testIsNodeColumn() { - TableImpl tableNode = new TableImpl(Node.class, false); - TableImpl tableEdge = new TableImpl(Edge.class, false); + TableImpl tableNode = new TableImpl(Node.class); + TableImpl tableEdge = new TableImpl(Edge.class); Column column = tableNode.addColumn("0", Integer.class); Assert.assertTrue(AttributeUtils.isNodeColumn(column)); @@ -809,8 +900,8 @@ public void testIsNodeColumn() { @Test public void testIsEdgeColumn() { - TableImpl tableNode = new TableImpl(Node.class, false); - TableImpl tableEdge = new TableImpl(Edge.class, false); + TableImpl tableNode = new TableImpl(Node.class); + TableImpl tableEdge = new TableImpl(Edge.class); Column column = tableEdge.addColumn("0", Integer.class); Assert.assertTrue(AttributeUtils.isEdgeColumn(column)); @@ -835,4 +926,97 @@ public void testIsSimpleType() { Assert.assertFalse(AttributeUtils.isSimpleType(TimestampBooleanMap.class)); Assert.assertFalse(AttributeUtils.isSimpleType(IntervalBooleanMap.class)); } + + @Test + public void testCopyNull() { + Assert.assertNull(AttributeUtils.copy(null)); + } + + @Test + public void testCopyPrimitive() { + String str = "foo"; + Assert.assertSame(AttributeUtils.copy(str), str); + int bar = 42; + Assert.assertSame(AttributeUtils.copy(bar), bar); + } + + @Test + public void testCopyIntervalSet() { + IntervalStringMap strMap = new IntervalStringMap(new double[] { 1.0, 2.0 }, new String[] { "foo" }); + IntervalByteMap byteMap = new IntervalByteMap(new double[] { 1.0, 2.0 }, new byte[] { 1 }); + IntervalShortMap shortMap = new IntervalShortMap(new double[] { 1.0, 2.0 }, new short[] { 1 }); + IntervalIntegerMap intMap = new IntervalIntegerMap(new double[] { 1.0, 2.0 }, new int[] { 1 }); + IntervalLongMap longMap = new IntervalLongMap(new double[] { 1.0, 2.0 }, new long[] { 1 }); + IntervalFloatMap floatMap = new IntervalFloatMap(new double[] { 1.0, 2.0 }, new float[] { 1 }); + IntervalDoubleMap doubleMap = new IntervalDoubleMap(new double[] { 1.0, 2.0 }, new double[] { 1 }); + IntervalBooleanMap boolMap = new IntervalBooleanMap(new double[] { 1.0, 2.0 }, new boolean[] { true }); + IntervalCharMap charMap = new IntervalCharMap(new double[] { 1.0, 2.0 }, new char[] { 'a' }); + + assertCopyIsEqualsButNotSame(strMap); + assertCopyIsEqualsButNotSame(byteMap); + assertCopyIsEqualsButNotSame(shortMap); + assertCopyIsEqualsButNotSame(intMap); + assertCopyIsEqualsButNotSame(longMap); + assertCopyIsEqualsButNotSame(floatMap); + assertCopyIsEqualsButNotSame(doubleMap); + assertCopyIsEqualsButNotSame(boolMap); + assertCopyIsEqualsButNotSame(charMap); + } + + @Test + public void testCopyTimestampSet() { + TimestampStringMap strMap = new TimestampStringMap(new double[] { 1.0 }, new String[] { "foo" }); + TimestampByteMap byteMap = new TimestampByteMap(new double[] { 1.0 }, new byte[] { 1 }); + TimestampShortMap shortMap = new TimestampShortMap(new double[] { 1.0 }, new short[] { 1 }); + TimestampIntegerMap intMap = new TimestampIntegerMap(new double[] { 1.0 }, new int[] { 1 }); + TimestampLongMap longMap = new TimestampLongMap(new double[] { 1.0 }, new long[] { 1 }); + TimestampFloatMap floatMap = new TimestampFloatMap(new double[] { 1.0 }, new float[] { 1 }); + TimestampDoubleMap doubleMap = new TimestampDoubleMap(new double[] { 1.0 }, new double[] { 1 }); + TimestampCharMap charMap = new TimestampCharMap(new double[] { 1.0 }, new char[] { 'a' }); + TimestampBooleanMap boolMap = new TimestampBooleanMap(new double[] { 1.0 }, new boolean[] { true }); + + assertCopyIsEqualsButNotSame(strMap); + assertCopyIsEqualsButNotSame(byteMap); + assertCopyIsEqualsButNotSame(shortMap); + assertCopyIsEqualsButNotSame(intMap); + assertCopyIsEqualsButNotSame(longMap); + assertCopyIsEqualsButNotSame(floatMap); + assertCopyIsEqualsButNotSame(doubleMap); + assertCopyIsEqualsButNotSame(charMap); + assertCopyIsEqualsButNotSame(boolMap); + } + + @Test + public void testCopyInstant() { + Instant instant = Instant.now(); + Assert.assertSame(AttributeUtils.copy(instant), instant); + } + + @Test + public void testCopyList() { + List list = new ArrayList(); + list.add("foo"); + assertCopyIsEqualsButNotSame(list); + } + + @Test + public void testCopySet() { + Set set = new HashSet(); + set.add("foo"); + assertCopyIsEqualsButNotSame(set); + } + + @Test + public void testCopyMap() { + Map map = new HashMap(); + map.put("foo", "bar"); + assertCopyIsEqualsButNotSame(map); + } + + // Utility + private void assertCopyIsEqualsButNotSame(Object o) { + Object copy = AttributeUtils.copy(o); + Assert.assertEquals(copy, o); + Assert.assertNotSame(copy, o); + } } diff --git a/store/src/test/java/org/gephi/graph/api/types/IntervalMapTest.java b/src/test/java/org/gephi/graph/api/types/IntervalMapTest.java similarity index 79% rename from store/src/test/java/org/gephi/graph/api/types/IntervalMapTest.java rename to src/test/java/org/gephi/graph/api/types/IntervalMapTest.java index 338cc2f2..10ed64f3 100644 --- a/store/src/test/java/org/gephi/graph/api/types/IntervalMapTest.java +++ b/src/test/java/org/gephi/graph/api/types/IntervalMapTest.java @@ -18,11 +18,12 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; +import java.time.ZoneId; +import java.time.ZonedDateTime; import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Estimator; import org.gephi.graph.api.Interval; import org.gephi.graph.api.TimeFormat; -import org.joda.time.DateTimeZone; import org.testng.Assert; import org.testng.annotations.Test; @@ -89,8 +90,8 @@ public void testMultiplePutWithOverlap() { Assert.assertTrue(set.put(new Interval(2.0, 2.0), defaultValues[0])); Assert.assertTrue(set.put(new Interval(2.0, 3.0), defaultValues[1])); defaultValues = new Object[] { defaultValues[0], defaultValues[0], defaultValues[1], defaultValues[1] }; - testValues(set, new Interval[] { new Interval(1.0, 2.0), new Interval(2.0, 2.0), new Interval(2.0, 3.0), new Interval( - 3.0, 4.0) }, defaultValues); + testValues(set, new Interval[] { new Interval(1.0, 2.0), new Interval(2.0, 2.0), new Interval(2.0, + 3.0), new Interval(3.0, 4.0) }, defaultValues); } } @@ -226,14 +227,22 @@ public void getIntervalsWeight() { j.put(new Interval(2003, 2004), 42); j.put(new Interval(2005, 2006), 42); - Assert.assertEquals(j.getIntervalsWeight(2000, 2002, j.getOverlappingIntervals(2000, 2002)), new double[] { 2.0 }); - Assert.assertEquals(j.getIntervalsWeight(2001, 2002, j.getOverlappingIntervals(2001, 2002)), new double[] { 1.0 }); - Assert.assertEquals(j.getIntervalsWeight(2000, 2001, j.getOverlappingIntervals(2000, 2001)), new double[] { 1.0 }); - Assert.assertEquals(j.getIntervalsWeight(2000.5, 2001.5, j.getOverlappingIntervals(2000.5, 2001.5)), new double[] { 1.0 }); - Assert.assertEquals(j.getIntervalsWeight(1999, 2000, j.getOverlappingIntervals(1999, 2000)), new double[] { 0 }); - Assert.assertEquals(j.getIntervalsWeight(1999, 2001, j.getOverlappingIntervals(1999, 2001)), new double[] { 1.0 }); - Assert.assertEquals(j.getIntervalsWeight(2000, 2003, j.getOverlappingIntervals(2000, 2003)), new double[] { 2.0, 0 }); - Assert.assertEquals(j.getIntervalsWeight(2000, 2004, j.getOverlappingIntervals(2000, 2004)), new double[] { 2.0, 1.0 }); + Assert.assertEquals(j + .getIntervalsWeight(2000, 2002, j.getOverlappingIntervals(2000, 2002)), new double[] { 2.0 }); + Assert.assertEquals(j + .getIntervalsWeight(2001, 2002, j.getOverlappingIntervals(2001, 2002)), new double[] { 1.0 }); + Assert.assertEquals(j + .getIntervalsWeight(2000, 2001, j.getOverlappingIntervals(2000, 2001)), new double[] { 1.0 }); + Assert.assertEquals(j + .getIntervalsWeight(2000.5, 2001.5, j.getOverlappingIntervals(2000.5, 2001.5)), new double[] { 1.0 }); + Assert.assertEquals(j + .getIntervalsWeight(1999, 2000, j.getOverlappingIntervals(1999, 2000)), new double[] { 0 }); + Assert.assertEquals(j + .getIntervalsWeight(1999, 2001, j.getOverlappingIntervals(1999, 2001)), new double[] { 1.0 }); + Assert.assertEquals(j + .getIntervalsWeight(2000, 2003, j.getOverlappingIntervals(2000, 2003)), new double[] { 2.0, 0 }); + Assert.assertEquals(j + .getIntervalsWeight(2000, 2004, j.getOverlappingIntervals(2000, 2004)), new double[] { 2.0, 1.0 }); } @Test @@ -406,8 +415,8 @@ public void testShortEstimators() { @Test public void testEquals() { - Interval[] indices = new Interval[] { new Interval(1.0, 2.0), new Interval(3.0, 4.0), new Interval(2.0, 2.0), new Interval( - 2.0, 3.0) }; + Interval[] indices = new Interval[] { new Interval(1.0, 2.0), new Interval(3.0, 4.0), new Interval(2.0, + 2.0), new Interval(2.0, 3.0) }; String[] values = new String[] { "a", "z", "e" }; IntervalStringMap set1 = new IntervalStringMap(); IntervalStringMap set2 = new IntervalStringMap(); @@ -505,7 +514,8 @@ public void testToStringDouble() { Assert.assertEquals(map1.toString(), "<[1.0, 2.0, foo]; [4.0, 5.5, bar]>"); map1.put(new Interval(6.0, 9.0), " 'test' "); - Assert.assertEquals(map1.toString(TimeFormat.DOUBLE), "<[1.0, 2.0, foo]; [4.0, 5.5, bar]; [6.0, 9.0, \" 'test' \"]>"); + Assert.assertEquals(map1 + .toString(TimeFormat.DOUBLE), "<[1.0, 2.0, foo]; [4.0, 5.5, bar]; [6.0, 9.0, \" 'test' \"]>"); } @Test @@ -513,18 +523,24 @@ public void testToStringDate() { IntervalStringMap map1 = new IntervalStringMap(); Assert.assertEquals(map1.toString(TimeFormat.DATE), ""); - map1.put(new Interval(AttributeUtils.parseDateTime("2012-02-29"), AttributeUtils.parseDateTime("2012-03-01")), "foo"); + map1.put(new Interval(AttributeUtils.parseDateTime("2012-02-29"), + AttributeUtils.parseDateTime("2012-03-01")), "foo"); Assert.assertEquals(map1.toString(TimeFormat.DATE), "<[2012-02-29, 2012-03-01, foo]>"); - map1.put(new Interval(AttributeUtils.parseDateTime("2012-07-17T00:02:21"), AttributeUtils - .parseDateTime("2012-07-17T00:03:00")), "bar"); - Assert.assertEquals(map1.toString(TimeFormat.DATE), "<[2012-02-29, 2012-03-01, foo]; [2012-07-17, 2012-07-17, bar]>"); - Assert.assertEquals(map1.toString(TimeFormat.DOUBLE), "<[1330473600000.0, 1330560000000.0, foo]; [1342483341000.0, 1342483380000.0, bar]>"); + map1.put(new Interval(AttributeUtils.parseDateTime("2012-07-17T00:02:21"), + AttributeUtils.parseDateTime("2012-07-17T00:03:00")), "bar"); + Assert.assertEquals(map1 + .toString(TimeFormat.DATE), "<[2012-02-29, 2012-03-01, foo]; [2012-07-17, 2012-07-17, bar]>"); + Assert.assertEquals(map1 + .toString(TimeFormat.DOUBLE), "<[1330473600000.0, 1330560000000.0, foo]; [1342483341000.0, 1342483380000.0, bar]>"); // Test with time zone printing: - Assert.assertEquals(map1.toString(TimeFormat.DATE, DateTimeZone.UTC), "<[2012-02-29, 2012-03-01, foo]; [2012-07-17, 2012-07-17, bar]>"); - Assert.assertEquals(map1.toString(TimeFormat.DATE, DateTimeZone.forID("+03:00")), "<[2012-02-29, 2012-03-01, foo]; [2012-07-17, 2012-07-17, bar]>"); - Assert.assertEquals(map1.toString(TimeFormat.DATE, DateTimeZone.forID("-03:00")), "<[2012-02-28, 2012-02-29, foo]; [2012-07-16, 2012-07-16, bar]>"); + Assert.assertEquals(map1.toString(TimeFormat.DATE, ZoneId + .of("UTC")), "<[2012-02-29, 2012-03-01, foo]; [2012-07-17, 2012-07-17, bar]>"); + Assert.assertEquals(map1.toString(TimeFormat.DATE, ZoneId + .of("+03:00")), "<[2012-02-29, 2012-03-01, foo]; [2012-07-17, 2012-07-17, bar]>"); + Assert.assertEquals(map1.toString(TimeFormat.DATE, ZoneId + .of("-03:00")), "<[2012-02-28, 2012-02-29, foo]; [2012-07-16, 2012-07-16, bar]>"); // Test infinity: IntervalStringMap mapInf = new IntervalStringMap(); @@ -538,28 +554,37 @@ public void testToStringDatetime() { Assert.assertEquals(map1.toString(TimeFormat.DATETIME), ""); // Test with default timezone UTC+0 - map1.put(new Interval(AttributeUtils.parseDateTime("2012-02-29"), AttributeUtils.parseDateTime("2012-03-01")), "foo"); - Assert.assertEquals(map1.toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, 2012-03-01T00:00:00.000Z, foo]>"); - - map1.put(new Interval(AttributeUtils.parseDateTime("2012-07-17T01:10:44"), AttributeUtils - .parseDateTime("2012-07-17T01:10:45")), "bar"); - Assert.assertEquals(map1.toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, 2012-03-01T00:00:00.000Z, foo]; [2012-07-17T01:10:44.000Z, 2012-07-17T01:10:45.000Z, bar]>"); - Assert.assertEquals(map1.toString(TimeFormat.DOUBLE), "<[1330473600000.0, 1330560000000.0, foo]; [1342487444000.0, 1342487445000.0, bar]>"); + map1.put(new Interval(AttributeUtils.parseDateTime("2012-02-29"), + AttributeUtils.parseDateTime("2012-03-01")), "foo"); + Assert.assertEquals(map1 + .toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, 2012-03-01T00:00:00.000Z, foo]>"); + + map1.put(new Interval(AttributeUtils.parseDateTime("2012-07-17T01:10:44"), + AttributeUtils.parseDateTime("2012-07-17T01:10:45")), "bar"); + Assert.assertEquals(map1 + .toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, 2012-03-01T00:00:00.000Z, foo]; [2012-07-17T01:10:44.000Z, 2012-07-17T01:10:45.000Z, bar]>"); + Assert.assertEquals(map1 + .toString(TimeFormat.DOUBLE), "<[1330473600000.0, 1330560000000.0, foo]; [1342487444000.0, 1342487445000.0, bar]>"); // Test with time zone printing: - Assert.assertEquals(map1.toString(TimeFormat.DATETIME, DateTimeZone.UTC), "<[2012-02-29T00:00:00.000Z, 2012-03-01T00:00:00.000Z, foo]; [2012-07-17T01:10:44.000Z, 2012-07-17T01:10:45.000Z, bar]>"); - Assert.assertEquals(map1.toString(TimeFormat.DATETIME, DateTimeZone.forID("+12:30")), "<[2012-02-29T12:30:00.000+12:30, 2012-03-01T12:30:00.000+12:30, foo]; [2012-07-17T13:40:44.000+12:30, 2012-07-17T13:40:45.000+12:30, bar]>"); + Assert.assertEquals(map1.toString(TimeFormat.DATETIME, ZoneId + .of("UTC")), "<[2012-02-29T00:00:00.000Z, 2012-03-01T00:00:00.000Z, foo]; [2012-07-17T01:10:44.000Z, 2012-07-17T01:10:45.000Z, bar]>"); + Assert.assertEquals(map1.toString(TimeFormat.DATETIME, ZoneId + .of("+12:30")), "<[2012-02-29T12:30:00.000+12:30, 2012-03-01T12:30:00.000+12:30, foo]; [2012-07-17T13:40:44.000+12:30, 2012-07-17T13:40:45.000+12:30, bar]>"); // Test with timezone parsing and UTC printing: IntervalStringMap map2 = new IntervalStringMap(); - map2.put(new Interval(AttributeUtils.parseDateTime("2012-02-29T00:00:00+02:30"), AttributeUtils - .parseDateTime("2012-02-29T02:30:00+02:30")), "foo"); - Assert.assertEquals(map2.toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, 2012-02-29T00:00:00.000Z, foo]>"); - - map2.put(new Interval(AttributeUtils.parseDateTime("2012-02-29T01:10:44+00:00"), AttributeUtils - .parseDateTime("2012-02-29T01:10:45+00:00")), "bar"); - Assert.assertEquals(map2.toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, 2012-02-29T00:00:00.000Z, foo]; [2012-02-29T01:10:44.000Z, 2012-02-29T01:10:45.000Z, bar]>"); - Assert.assertEquals(map2.toString(TimeFormat.DOUBLE), "<[1330464600000.0, 1330473600000.0, foo]; [1330477844000.0, 1330477845000.0, bar]>"); + map2.put(new Interval(AttributeUtils.parseDateTime("2012-02-29T00:00:00+02:30"), + AttributeUtils.parseDateTime("2012-02-29T02:30:00+02:30")), "foo"); + Assert.assertEquals(map2 + .toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, 2012-02-29T00:00:00.000Z, foo]>"); + + map2.put(new Interval(AttributeUtils.parseDateTime("2012-02-29T01:10:44+00:00"), + AttributeUtils.parseDateTime("2012-02-29T01:10:45+00:00")), "bar"); + Assert.assertEquals(map2 + .toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, 2012-02-29T00:00:00.000Z, foo]; [2012-02-29T01:10:44.000Z, 2012-02-29T01:10:45.000Z, bar]>"); + Assert.assertEquals(map2 + .toString(TimeFormat.DOUBLE), "<[1330464600000.0, 1330473600000.0, foo]; [1330477844000.0, 1330477845000.0, bar]>"); // Test infinity: IntervalStringMap mapInf = new IntervalStringMap(); @@ -567,6 +592,29 @@ public void testToStringDatetime() { Assert.assertEquals(mapInf.toString(TimeFormat.DATETIME), "<[-Infinity, Infinity, value]>"); } + @Test + public void testCopy() { + IntervalStringMap strMap = new IntervalStringMap(new double[] { 1.0, 2.0 }, new String[] { "foo" }); + IntervalByteMap byteMap = new IntervalByteMap(new double[] { 1.0, 2.0 }, new byte[] { 1 }); + IntervalShortMap shortMap = new IntervalShortMap(new double[] { 1.0, 2.0 }, new short[] { 1 }); + IntervalIntegerMap intMap = new IntervalIntegerMap(new double[] { 1.0, 2.0 }, new int[] { 1 }); + IntervalLongMap longMap = new IntervalLongMap(new double[] { 1.0, 2.0 }, new long[] { 1 }); + IntervalFloatMap floatMap = new IntervalFloatMap(new double[] { 1.0, 2.0 }, new float[] { 1 }); + IntervalDoubleMap doubleMap = new IntervalDoubleMap(new double[] { 1.0, 2.0 }, new double[] { 1 }); + IntervalBooleanMap boolMap = new IntervalBooleanMap(new double[] { 1.0, 2.0 }, new boolean[] { true }); + IntervalCharMap charMap = new IntervalCharMap(new double[] { 1.0, 2.0 }, new char[] { 'a' }); + + testEqualsButNotSameUnderlyingArrays(new IntervalStringMap(strMap), strMap); + testEqualsButNotSameUnderlyingArrays(new IntervalByteMap(byteMap), byteMap); + testEqualsButNotSameUnderlyingArrays(new IntervalShortMap(shortMap), shortMap); + testEqualsButNotSameUnderlyingArrays(new IntervalIntegerMap(intMap), intMap); + testEqualsButNotSameUnderlyingArrays(new IntervalLongMap(longMap), longMap); + testEqualsButNotSameUnderlyingArrays(new IntervalFloatMap(floatMap), floatMap); + testEqualsButNotSameUnderlyingArrays(new IntervalDoubleMap(doubleMap), doubleMap); + testEqualsButNotSameUnderlyingArrays(new IntervalBooleanMap(boolMap), boolMap); + testEqualsButNotSameUnderlyingArrays(new IntervalCharMap(charMap), charMap); + } + // UTILITY private void testDoubleArrayEquals(double[] a, double[] b) { Assert.assertEquals(a.length, b.length); @@ -575,14 +623,21 @@ private void testDoubleArrayEquals(double[] a, double[] b) { } } + private void testEqualsButNotSameUnderlyingArrays(IntervalMap a, IntervalMap b) { + Assert.assertEquals(a, b); + Assert.assertNotSame(a.array, b.array); + Assert.assertNotSame(a.getValuesArray(), b.getValuesArray()); + } + private IntervalMap[] getAllInstances() { return new IntervalMap[] { new IntervalStringMap(), new IntervalBooleanMap(), new IntervalFloatMap(), new IntervalDoubleMap(), new IntervalIntegerMap(), new IntervalShortMap(), new IntervalLongMap(), new IntervalByteMap(), new IntervalCharMap() }; } private IntervalMap[] getAllInstances(int capacity) { - return new IntervalMap[] { new IntervalStringMap(capacity), new IntervalBooleanMap(capacity), new IntervalFloatMap( - capacity), new IntervalDoubleMap(capacity), new IntervalIntegerMap(capacity), new IntervalShortMap( - capacity), new IntervalLongMap(capacity), new IntervalByteMap(capacity), new IntervalCharMap(capacity) }; + return new IntervalMap[] { new IntervalStringMap(capacity), new IntervalBooleanMap( + capacity), new IntervalFloatMap(capacity), new IntervalDoubleMap(capacity), new IntervalIntegerMap( + capacity), new IntervalShortMap(capacity), new IntervalLongMap( + capacity), new IntervalByteMap(capacity), new IntervalCharMap(capacity) }; } private Object[] getTestValues(IntervalMap set) { @@ -650,7 +705,8 @@ private void testValues(IntervalMap set, Interval[] expectedIntervals, Object[] .getMethod("get" + typeClass.getSimpleName(), Interval.class, getMethod.getReturnType()); Assert.assertEquals(getMethod.invoke(set, expectedIntervals[i]), expectedValues[i]); - Assert.assertEquals(getMethodWithDefault.invoke(set, expectedIntervals[i], getDefaultValue(set)), expectedValues[i]); + Assert.assertEquals(getMethodWithDefault + .invoke(set, expectedIntervals[i], getDefaultValue(set)), expectedValues[i]); Assert.assertEquals(getMethodWithDefault .invoke(set, new Interval(99999.0, 999999.0), getDefaultValue(set)), getDefaultValue(set)); diff --git a/store/src/test/java/org/gephi/graph/api/types/IntervalSetTest.java b/src/test/java/org/gephi/graph/api/types/IntervalSetTest.java similarity index 77% rename from store/src/test/java/org/gephi/graph/api/types/IntervalSetTest.java rename to src/test/java/org/gephi/graph/api/types/IntervalSetTest.java index b097ed03..6ed67915 100644 --- a/store/src/test/java/org/gephi/graph/api/types/IntervalSetTest.java +++ b/src/test/java/org/gephi/graph/api/types/IntervalSetTest.java @@ -15,10 +15,11 @@ */ package org.gephi.graph.api.types; +import java.time.ZoneId; +import java.time.ZonedDateTime; import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Interval; import org.gephi.graph.api.TimeFormat; -import org.joda.time.DateTimeZone; import org.testng.Assert; import org.testng.annotations.Test; @@ -93,7 +94,7 @@ public void testDuplicate() { } @Test - public void testContinous() { + public void testContinuous() { IntervalSet set = new IntervalSet(); Assert.assertTrue(set.add(new Interval(0.0, 1.0))); Assert.assertTrue(set.add(new Interval(1.0, 2.0))); @@ -109,6 +110,32 @@ public void testContinous() { Assert.assertTrue(set.contains(new Interval(1.0, 1.0))); } + @Test + public void testSorted() { + IntervalSet set = new IntervalSet(); + set.add(new Interval(4.0, 5.0)); + set.add(new Interval(1.0, 4.0)); + + Assert.assertEquals(1.0, set.toArray()[0].getLow()); + Assert.assertEquals(5.0, set.toArray()[1].getHigh()); + } + + @Test + public void testMinMax() { + IntervalSet set = new IntervalSet(); + Assert.assertNull(set.getMax()); + Assert.assertNull(set.getMin()); + Assert.assertNull(set.getMaxDouble()); + Assert.assertNull(set.getMinDouble()); + + set.add(new Interval(4.0, 5.0)); + set.add(new Interval(1.0, 4.0)); + Assert.assertEquals(set.getMin().getLow(), 1.0); + Assert.assertEquals(set.getMax().getHigh(), 5.0); + Assert.assertEquals(set.getMinDouble(), 1.0); + Assert.assertEquals(set.getMaxDouble(), 5.0); + } + @Test(expectedExceptions = IllegalArgumentException.class) public void testStartOverlappingAbove() { IntervalSet set = new IntervalSet(); @@ -333,18 +360,23 @@ public void testToStringDate() { set1.add(new Interval(AttributeUtils.parseDateTime("2012-02-29"), AttributeUtils.parseDateTime("2012-03-01"))); Assert.assertEquals(set1.toString(TimeFormat.DATE), "<[2012-02-29, 2012-03-01]>"); - set1.add(new Interval(AttributeUtils.parseDateTime("2012-07-17T00:02:21"), AttributeUtils - .parseDateTime("2012-07-17T00:03:00"))); + set1.add(new Interval(AttributeUtils.parseDateTime("2012-07-17T00:02:21"), + AttributeUtils.parseDateTime("2012-07-17T00:03:00"))); Assert.assertEquals(set1.toString(TimeFormat.DATE), "<[2012-02-29, 2012-03-01]; [2012-07-17, 2012-07-17]>"); - Assert.assertEquals(set1.toString(TimeFormat.DOUBLE), "<[1330473600000.0, 1330560000000.0]; [1342483341000.0, 1342483380000.0]>"); + Assert.assertEquals(set1 + .toString(TimeFormat.DOUBLE), "<[1330473600000.0, 1330560000000.0]; [1342483341000.0, 1342483380000.0]>"); // Test with time zone printing: - Assert.assertEquals(set1.toString(TimeFormat.DATE, DateTimeZone.UTC), "<[2012-02-29, 2012-03-01]; [2012-07-17, 2012-07-17]>"); - Assert.assertEquals(set1.toString(TimeFormat.DATE, DateTimeZone.forID("+12:00")), "<[2012-02-29, 2012-03-01]; [2012-07-17, 2012-07-17]>"); - set1.add(new Interval(AttributeUtils.parseDateTime("2012-07-18T18:30:00"), AttributeUtils - .parseDateTime("2012-07-18T18:30:01"))); - Assert.assertEquals(set1.toString(TimeFormat.DATE, DateTimeZone.forID("+08:00")), "<[2012-02-29, 2012-03-01]; [2012-07-17, 2012-07-17]; [2012-07-19, 2012-07-19]>"); - Assert.assertEquals(set1.toString(TimeFormat.DATE, DateTimeZone.forID("-10:00")), "<[2012-02-28, 2012-02-29]; [2012-07-16, 2012-07-16]; [2012-07-18, 2012-07-18]>"); + Assert.assertEquals(set1 + .toString(TimeFormat.DATE, ZoneId.of("UTC")), "<[2012-02-29, 2012-03-01]; [2012-07-17, 2012-07-17]>"); + Assert.assertEquals(set1.toString(TimeFormat.DATE, ZoneId + .of("+12:00")), "<[2012-02-29, 2012-03-01]; [2012-07-17, 2012-07-17]>"); + set1.add(new Interval(AttributeUtils.parseDateTime("2012-07-18T18:30:00"), + AttributeUtils.parseDateTime("2012-07-18T18:30:01"))); + Assert.assertEquals(set1.toString(TimeFormat.DATE, ZoneId + .of("+08:00")), "<[2012-02-29, 2012-03-01]; [2012-07-17, 2012-07-17]; [2012-07-19, 2012-07-19]>"); + Assert.assertEquals(set1.toString(TimeFormat.DATE, ZoneId + .of("-10:00")), "<[2012-02-28, 2012-02-29]; [2012-07-16, 2012-07-16]; [2012-07-18, 2012-07-18]>"); // Test infinity: IntervalSet setInf = new IntervalSet(); @@ -359,31 +391,49 @@ public void testToStringDatetime() { // Test with default timezone UTC+0 set1.add(new Interval(AttributeUtils.parseDateTime("2012-02-29"), AttributeUtils.parseDateTime("2012-03-01"))); - Assert.assertEquals(set1.toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, 2012-03-01T00:00:00.000Z]>"); + Assert.assertEquals(set1 + .toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, 2012-03-01T00:00:00.000Z]>"); - set1.add(new Interval(AttributeUtils.parseDateTime("2012-07-17T01:10:44"), AttributeUtils - .parseDateTime("2012-07-17T01:10:45"))); - Assert.assertEquals(set1.toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, 2012-03-01T00:00:00.000Z]; [2012-07-17T01:10:44.000Z, 2012-07-17T01:10:45.000Z]>"); - Assert.assertEquals(set1.toString(TimeFormat.DOUBLE), "<[1330473600000.0, 1330560000000.0]; [1342487444000.0, 1342487445000.0]>"); + set1.add(new Interval(AttributeUtils.parseDateTime("2012-07-17T01:10:44"), + AttributeUtils.parseDateTime("2012-07-17T01:10:45"))); + Assert.assertEquals(set1 + .toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, 2012-03-01T00:00:00.000Z]; [2012-07-17T01:10:44.000Z, 2012-07-17T01:10:45.000Z]>"); + Assert.assertEquals(set1 + .toString(TimeFormat.DOUBLE), "<[1330473600000.0, 1330560000000.0]; [1342487444000.0, 1342487445000.0]>"); // Test with time zone printing: - Assert.assertEquals(set1.toString(TimeFormat.DATETIME, DateTimeZone.UTC), "<[2012-02-29T00:00:00.000Z, 2012-03-01T00:00:00.000Z]; [2012-07-17T01:10:44.000Z, 2012-07-17T01:10:45.000Z]>"); - Assert.assertEquals(set1.toString(TimeFormat.DATETIME, DateTimeZone.forID("+12:00")), "<[2012-02-29T12:00:00.000+12:00, 2012-03-01T12:00:00.000+12:00]; [2012-07-17T13:10:44.000+12:00, 2012-07-17T13:10:45.000+12:00]>"); + Assert.assertEquals(set1.toString(TimeFormat.DATETIME, ZoneId + .of("UTC")), "<[2012-02-29T00:00:00.000Z, 2012-03-01T00:00:00.000Z]; [2012-07-17T01:10:44.000Z, 2012-07-17T01:10:45.000Z]>"); + Assert.assertEquals(set1.toString(TimeFormat.DATETIME, ZoneId + .of("+12:00")), "<[2012-02-29T12:00:00.000+12:00, 2012-03-01T12:00:00.000+12:00]; [2012-07-17T13:10:44.000+12:00, 2012-07-17T13:10:45.000+12:00]>"); // Test with timezone parsing and UTC printing: IntervalSet set2 = new IntervalSet(); - set2.add(new Interval(AttributeUtils.parseDateTime("2012-02-29T00:00:00+02:30"), AttributeUtils - .parseDateTime("2012-02-29T02:30:00+02:30"))); - Assert.assertEquals(set2.toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, 2012-02-29T00:00:00.000Z]>"); - - set2.add(new Interval(AttributeUtils.parseDateTime("2012-02-29T01:10:44+00:00"), AttributeUtils - .parseDateTime("2012-02-29T01:10:45+00:00"))); - Assert.assertEquals(set2.toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, 2012-02-29T00:00:00.000Z]; [2012-02-29T01:10:44.000Z, 2012-02-29T01:10:45.000Z]>"); - Assert.assertEquals(set2.toString(TimeFormat.DOUBLE), "<[1330464600000.0, 1330473600000.0]; [1330477844000.0, 1330477845000.0]>"); + set2.add(new Interval(AttributeUtils.parseDateTime("2012-02-29T00:00:00+02:30"), + AttributeUtils.parseDateTime("2012-02-29T02:30:00+02:30"))); + Assert.assertEquals(set2 + .toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, 2012-02-29T00:00:00.000Z]>"); + + set2.add(new Interval(AttributeUtils.parseDateTime("2012-02-29T01:10:44+00:00"), + AttributeUtils.parseDateTime("2012-02-29T01:10:45+00:00"))); + Assert.assertEquals(set2 + .toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, 2012-02-29T00:00:00.000Z]; [2012-02-29T01:10:44.000Z, 2012-02-29T01:10:45.000Z]>"); + Assert.assertEquals(set2 + .toString(TimeFormat.DOUBLE), "<[1330464600000.0, 1330473600000.0]; [1330477844000.0, 1330477845000.0]>"); // Test infinity: IntervalSet setInf = new IntervalSet(); setInf.add(new Interval(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)); Assert.assertEquals(setInf.toString(TimeFormat.DATETIME), "<[-Infinity, Infinity]>"); } + + @Test + public void testCopy() { + IntervalSet set1 = new IntervalSet(); + set1.add(new Interval(1.0, 2.0)); + IntervalSet set2 = new IntervalSet(set1); + Assert.assertEquals(set2, set1); + set1.add(new Interval(3.0, 4.0)); + Assert.assertNotEquals(set2, set1); + } } diff --git a/store/src/test/java/org/gephi/graph/api/types/TimestampMapTest.java b/src/test/java/org/gephi/graph/api/types/TimestampMapTest.java similarity index 88% rename from store/src/test/java/org/gephi/graph/api/types/TimestampMapTest.java rename to src/test/java/org/gephi/graph/api/types/TimestampMapTest.java index d0120129..3ed2ab6a 100644 --- a/store/src/test/java/org/gephi/graph/api/types/TimestampMapTest.java +++ b/src/test/java/org/gephi/graph/api/types/TimestampMapTest.java @@ -17,11 +17,12 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.time.ZoneId; +import java.time.ZonedDateTime; import org.gephi.graph.api.AttributeUtils; import org.gephi.graph.api.Estimator; import org.gephi.graph.api.Interval; import org.gephi.graph.api.TimeFormat; -import org.joda.time.DateTimeZone; import org.testng.Assert; import org.testng.annotations.Test; @@ -625,7 +626,8 @@ public void testToStringDouble() { map1.put(6.0, " 'test' "); map1.put(9.0, " 'test' "); - Assert.assertEquals(map1.toString(TimeFormat.DOUBLE), "<[1.0, foo]; [5.5, bar]; [6.0, \" 'test' \"]; [9.0, \" 'test' \"]>"); + Assert.assertEquals(map1 + .toString(TimeFormat.DOUBLE), "<[1.0, foo]; [5.5, bar]; [6.0, \" 'test' \"]; [9.0, \" 'test' \"]>"); } @Test @@ -641,9 +643,11 @@ public void testToStringDate() { Assert.assertEquals(map1.toString(TimeFormat.DOUBLE), "<[1330473600000.0, foo]; [1330473741000.0, bar]>"); // Test with time zone printing: - Assert.assertEquals(map1.toString(TimeFormat.DATE, DateTimeZone.UTC), "<[2012-02-29, foo]; [2012-02-29, bar]>"); - Assert.assertEquals(map1.toString(TimeFormat.DATE, DateTimeZone.forID("+03:00")), "<[2012-02-29, foo]; [2012-02-29, bar]>"); - Assert.assertEquals(map1.toString(TimeFormat.DATE, DateTimeZone.forID("-03:00")), "<[2012-02-28, foo]; [2012-02-28, bar]>"); + Assert.assertEquals(map1.toString(TimeFormat.DATE, ZoneId.of("UTC")), "<[2012-02-29, foo]; [2012-02-29, bar]>"); + Assert.assertEquals(map1 + .toString(TimeFormat.DATE, ZoneId.of("+03:00")), "<[2012-02-29, foo]; [2012-02-29, bar]>"); + Assert.assertEquals(map1 + .toString(TimeFormat.DATE, ZoneId.of("-03:00")), "<[2012-02-28, foo]; [2012-02-28, bar]>"); // Test infinity: TimestampStringMap mapInf = new TimestampStringMap(); @@ -662,12 +666,15 @@ public void testToStringDatetime() { Assert.assertEquals(map1.toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, foo]>"); map1.put(AttributeUtils.parseDateTime("2012-02-29T01:10:44"), "bar"); - Assert.assertEquals(map1.toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, foo]; [2012-02-29T01:10:44.000Z, bar]>"); + Assert.assertEquals(map1 + .toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, foo]; [2012-02-29T01:10:44.000Z, bar]>"); Assert.assertEquals(map1.toString(TimeFormat.DOUBLE), "<[1330473600000.0, foo]; [1330477844000.0, bar]>"); // Test with time zone printing: - Assert.assertEquals(map1.toString(TimeFormat.DATETIME, DateTimeZone.UTC), "<[2012-02-29T00:00:00.000Z, foo]; [2012-02-29T01:10:44.000Z, bar]>"); - Assert.assertEquals(map1.toString(TimeFormat.DATETIME, DateTimeZone.forID("-01:30")), "<[2012-02-28T22:30:00.000-01:30, foo]; [2012-02-28T23:40:44.000-01:30, bar]>"); + Assert.assertEquals(map1.toString(TimeFormat.DATETIME, ZoneId + .of("UTC")), "<[2012-02-29T00:00:00.000Z, foo]; [2012-02-29T01:10:44.000Z, bar]>"); + Assert.assertEquals(map1.toString(TimeFormat.DATETIME, ZoneId + .of("-01:30")), "<[2012-02-28T22:30:00.000-01:30, foo]; [2012-02-28T23:40:44.000-01:30, bar]>"); // Test with timezone parsing and UTC printing: TimestampStringMap map2 = new TimestampStringMap(); @@ -675,7 +682,8 @@ public void testToStringDatetime() { Assert.assertEquals(map2.toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, foo]>"); map2.put(AttributeUtils.parseDateTime("2012-02-29T01:10:44-01:00"), "bar"); - Assert.assertEquals(map2.toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, foo]; [2012-02-29T02:10:44.000Z, bar]>"); + Assert.assertEquals(map2 + .toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, foo]; [2012-02-29T02:10:44.000Z, bar]>"); Assert.assertEquals(map2.toString(TimeFormat.DOUBLE), "<[1330464600000.0, foo]; [1330481444000.0, bar]>"); // Test infinity: @@ -685,6 +693,29 @@ public void testToStringDatetime() { Assert.assertEquals(mapInf.toString(TimeFormat.DATETIME), "<[-Infinity, value]; [Infinity, value]>"); } + @Test + public void testCopy() { + TimestampStringMap strMap = new TimestampStringMap(new double[] { 1.0 }, new String[] { "foo" }); + TimestampByteMap byteMap = new TimestampByteMap(new double[] { 1.0 }, new byte[] { 1 }); + TimestampShortMap shortMap = new TimestampShortMap(new double[] { 1.0 }, new short[] { 1 }); + TimestampIntegerMap intMap = new TimestampIntegerMap(new double[] { 1.0 }, new int[] { 1 }); + TimestampLongMap longMap = new TimestampLongMap(new double[] { 1.0 }, new long[] { 1 }); + TimestampFloatMap floatMap = new TimestampFloatMap(new double[] { 1.0 }, new float[] { 1 }); + TimestampDoubleMap doubleMap = new TimestampDoubleMap(new double[] { 1.0 }, new double[] { 1 }); + TimestampCharMap charMap = new TimestampCharMap(new double[] { 1.0 }, new char[] { 'a' }); + TimestampBooleanMap boolMap = new TimestampBooleanMap(new double[] { 1.0 }, new boolean[] { true }); + + testEqualsButNotSameUnderlyingArrays(new TimestampStringMap(strMap), strMap); + testEqualsButNotSameUnderlyingArrays(new TimestampByteMap(byteMap), byteMap); + testEqualsButNotSameUnderlyingArrays(new TimestampShortMap(shortMap), shortMap); + testEqualsButNotSameUnderlyingArrays(new TimestampIntegerMap(intMap), intMap); + testEqualsButNotSameUnderlyingArrays(new TimestampLongMap(longMap), longMap); + testEqualsButNotSameUnderlyingArrays(new TimestampFloatMap(floatMap), floatMap); + testEqualsButNotSameUnderlyingArrays(new TimestampDoubleMap(doubleMap), doubleMap); + testEqualsButNotSameUnderlyingArrays(new TimestampCharMap(charMap), charMap); + testEqualsButNotSameUnderlyingArrays(new TimestampBooleanMap(boolMap), boolMap); + } + // UTILITY private void testDoubleArrayEquals(double[] a, double[] b) { Assert.assertEquals(a.length, b.length); @@ -693,15 +724,21 @@ private void testDoubleArrayEquals(double[] a, double[] b) { } } + private void testEqualsButNotSameUnderlyingArrays(TimestampMap a, TimestampMap b) { + Assert.assertEquals(a, b); + Assert.assertNotSame(a.array, b.array); + Assert.assertNotSame(a.getValuesArray(), b.getValuesArray()); + } + private TimestampMap[] getAllInstances() { return new TimestampMap[] { new TimestampDoubleMap(), new TimestampByteMap(), new TimestampFloatMap(), new TimestampIntegerMap(), new TimestampLongMap(), new TimestampShortMap(), new TimestampStringMap(), new TimestampCharMap(), new TimestampBooleanMap() }; } private TimestampMap[] getAllInstances(int capacity) { - return new TimestampMap[] { new TimestampDoubleMap(capacity), new TimestampByteMap(capacity), new TimestampFloatMap( - capacity), new TimestampIntegerMap(capacity), new TimestampLongMap(capacity), new TimestampShortMap( - capacity), new TimestampStringMap(capacity), new TimestampCharMap(capacity), new TimestampBooleanMap( - capacity) }; + return new TimestampMap[] { new TimestampDoubleMap(capacity), new TimestampByteMap( + capacity), new TimestampFloatMap(capacity), new TimestampIntegerMap(capacity), new TimestampLongMap( + capacity), new TimestampShortMap(capacity), new TimestampStringMap( + capacity), new TimestampCharMap(capacity), new TimestampBooleanMap(capacity) }; } private Object[] getTestValues(TimestampMap set) { @@ -762,7 +799,7 @@ private void testValues(TimestampMap set, double[] expectedTimestamp, Object[] e Assert.assertEquals(set.get(expectedTimestamp[i], null), expectedValues[i]); Assert.assertEquals(set.get(999999.0, getDefaultValue(set)), getDefaultValue(set)); Assert.assertTrue(set.contains(expectedTimestamp[i])); - Assert.assertEquals(keysArray[i], expectedTimestamp[i]); + Assert.assertEquals(keysArray[i], expectedTimestamp[i], 0.0); if (typeClass != String.class) { try { @@ -771,8 +808,10 @@ private void testValues(TimestampMap set, double[] expectedTimestamp, Object[] e .getMethod("get" + typeClass.getSimpleName(), double.class, getMethod.getReturnType()); Assert.assertEquals(getMethod.invoke(set, expectedTimestamp[i]), expectedValues[i]); - Assert.assertEquals(getMethodWithDefault.invoke(set, expectedTimestamp[i], getDefaultValue(set)), expectedValues[i]); - Assert.assertEquals(getMethodWithDefault.invoke(set, 999999.0, getDefaultValue(set)), getDefaultValue(set)); + Assert.assertEquals(getMethodWithDefault + .invoke(set, expectedTimestamp[i], getDefaultValue(set)), expectedValues[i]); + Assert.assertEquals(getMethodWithDefault + .invoke(set, 999999.0, getDefaultValue(set)), getDefaultValue(set)); boolean thrown = false; try { diff --git a/store/src/test/java/org/gephi/graph/api/types/TimestampSetTest.java b/src/test/java/org/gephi/graph/api/types/TimestampSetTest.java similarity index 80% rename from store/src/test/java/org/gephi/graph/api/types/TimestampSetTest.java rename to src/test/java/org/gephi/graph/api/types/TimestampSetTest.java index 8cd950be..3b801a19 100644 --- a/store/src/test/java/org/gephi/graph/api/types/TimestampSetTest.java +++ b/src/test/java/org/gephi/graph/api/types/TimestampSetTest.java @@ -17,11 +17,13 @@ import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet; import it.unimi.dsi.fastutil.doubles.DoubleSet; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Random; import org.gephi.graph.api.AttributeUtils; +import org.gephi.graph.api.Interval; import org.gephi.graph.api.TimeFormat; import org.gephi.graph.impl.NumberGenerator; -import org.joda.time.DateTimeZone; import org.testng.Assert; import org.testng.annotations.Test; @@ -155,7 +157,8 @@ public void testRemoveAddLoop() { } } - testDoubleArrayEquals(set.toPrimitiveArray(), NumberGenerator.sortAndRemoveDuplicates(doubleSet.toDoubleArray())); + testDoubleArrayEquals(set.toPrimitiveArray(), NumberGenerator + .sortAndRemoveDuplicates(doubleSet.toDoubleArray())); double[] newArray = NumberGenerator.generateRandomDouble(count / 2, true); for (int i = 0; i < count / 2; i++) { @@ -164,7 +167,36 @@ public void testRemoveAddLoop() { doubleSet.add(number); } - testDoubleArrayEquals(set.toPrimitiveArray(), NumberGenerator.sortAndRemoveDuplicates(doubleSet.toDoubleArray())); + testDoubleArrayEquals(set.toPrimitiveArray(), NumberGenerator + .sortAndRemoveDuplicates(doubleSet.toDoubleArray())); + } + + @Test + public void testSorted() { + TimestampSet set = new TimestampSet(); + set.add(3.0); + set.add(1.0); + set.add(2.0); + + testDoubleArrayEquals(set.toPrimitiveArray(), new double[] { 1.0, 2.0, 3.0 }); + set.remove(1.0); + testDoubleArrayEquals(set.toPrimitiveArray(), new double[] { 2.0, 3.0 }); + } + + @Test + public void testMinMax() { + TimestampSet set = new TimestampSet(); + Assert.assertNull(set.getMax()); + Assert.assertNull(set.getMin()); + Assert.assertNull(set.getMaxDouble()); + Assert.assertNull(set.getMinDouble()); + + set.add(3.0); + set.add(1.0); + Assert.assertEquals(set.getMin(), 1.0); + Assert.assertEquals(set.getMax(), 3.0); + Assert.assertEquals(set.getMinDouble(), 1.0); + Assert.assertEquals(set.getMaxDouble(), 3.0); } @Test @@ -292,11 +324,13 @@ public void testToStringDate() { Assert.assertEquals(set1.toString(TimeFormat.DOUBLE), "<[1330473600000.0, 1330473741000.0]>"); // Test with time zone printing: - Assert.assertEquals(set1.toString(TimeFormat.DATE, DateTimeZone.UTC), "<[2012-02-29, 2012-02-29]>"); - Assert.assertEquals(set1.toString(TimeFormat.DATE, DateTimeZone.forID("+12:00")), "<[2012-02-29, 2012-02-29]>"); + Assert.assertEquals(set1.toString(TimeFormat.DATE, ZoneId.of("UTC")), "<[2012-02-29, 2012-02-29]>"); + Assert.assertEquals(set1.toString(TimeFormat.DATE, ZoneId.of("+12:00")), "<[2012-02-29, 2012-02-29]>"); set1.add(AttributeUtils.parseDateTime("2012-07-18T18:30:00")); - Assert.assertEquals(set1.toString(TimeFormat.DATE, DateTimeZone.forID("+08:00")), "<[2012-02-29, 2012-02-29, 2012-07-19]>"); - Assert.assertEquals(set1.toString(TimeFormat.DATE, DateTimeZone.forID("-10:00")), "<[2012-02-28, 2012-02-28, 2012-07-18]>"); + Assert.assertEquals(set1 + .toString(TimeFormat.DATE, ZoneId.of("+08:00")), "<[2012-02-29, 2012-02-29, 2012-07-19]>"); + Assert.assertEquals(set1 + .toString(TimeFormat.DATE, ZoneId.of("-10:00")), "<[2012-02-28, 2012-02-28, 2012-07-18]>"); // Test infinity: TimestampSet setInf = new TimestampSet(); @@ -315,12 +349,15 @@ public void testToStringDatetime() { Assert.assertEquals(set1.toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z]>"); set1.add(AttributeUtils.parseDateTime("2012-02-29T01:10:44")); - Assert.assertEquals(set1.toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, 2012-02-29T01:10:44.000Z]>"); + Assert.assertEquals(set1 + .toString(TimeFormat.DATETIME), "<[2012-02-29T00:00:00.000Z, 2012-02-29T01:10:44.000Z]>"); Assert.assertEquals(set1.toString(TimeFormat.DOUBLE), "<[1330473600000.0, 1330477844000.0]>"); // Test with time zone printing: - Assert.assertEquals(set1.toString(TimeFormat.DATETIME, DateTimeZone.UTC), "<[2012-02-29T00:00:00.000Z, 2012-02-29T01:10:44.000Z]>"); - Assert.assertEquals(set1.toString(TimeFormat.DATETIME, DateTimeZone.forID("+12:15")), "<[2012-02-29T12:15:00.000+12:15, 2012-02-29T13:25:44.000+12:15]>"); + Assert.assertEquals(set1.toString(TimeFormat.DATETIME, ZoneId + .of("UTC")), "<[2012-02-29T00:00:00.000Z, 2012-02-29T01:10:44.000Z]>"); + Assert.assertEquals(set1.toString(TimeFormat.DATETIME, ZoneId + .of("+12:15")), "<[2012-02-29T12:15:00.000+12:15, 2012-02-29T13:25:44.000+12:15]>"); // Test with timezone parsing and UTC printing: TimestampSet set2 = new TimestampSet(); @@ -328,7 +365,8 @@ public void testToStringDatetime() { Assert.assertEquals(set2.toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z]>"); set2.add(AttributeUtils.parseDateTime("2012-02-29T01:10:44-01:00")); - Assert.assertEquals(set2.toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, 2012-02-29T02:10:44.000Z]>"); + Assert.assertEquals(set2 + .toString(TimeFormat.DATETIME), "<[2012-02-28T21:30:00.000Z, 2012-02-29T02:10:44.000Z]>"); Assert.assertEquals(set2.toString(TimeFormat.DOUBLE), "<[1330464600000.0, 1330481444000.0]>"); // Test infinity: @@ -338,6 +376,15 @@ public void testToStringDatetime() { Assert.assertEquals(setInf.toString(TimeFormat.DATETIME), "<[-Infinity, Infinity]>"); } + @Test + public void testCopy() { + TimestampSet set1 = new TimestampSet(); + set1.add(1.0); + TimestampSet set2 = new TimestampSet(set1); + Assert.assertEquals(set2, set1); + Assert.assertNotSame(set2.toPrimitiveArray(), set1.toPrimitiveArray()); + } + // UTILITY private void testDoubleArrayEquals(double[] a, double[] b) { Assert.assertEquals(a.length, b.length); diff --git a/store/src/test/java/org/gephi/graph/impl/ArraysParserTest.java b/src/test/java/org/gephi/graph/impl/ArraysParserTest.java similarity index 91% rename from store/src/test/java/org/gephi/graph/impl/ArraysParserTest.java rename to src/test/java/org/gephi/graph/impl/ArraysParserTest.java index 8fdcf16e..d4c49493 100644 --- a/store/src/test/java/org/gephi/graph/impl/ArraysParserTest.java +++ b/src/test/java/org/gephi/graph/impl/ArraysParserTest.java @@ -46,6 +46,15 @@ public void testParseString() { Assert.assertEquals(new String[] { "-1", " value", "value2" }, a1); } + @Test + public void testParseStringWithParenthesis() { + String[] a1 = ArraysParser.parseArray(String[].class, "[Foo,Bar(Foo)]"); + String[] a2 = ArraysParser.parseArray(String[].class, "[(Foo), Bar(Foo), Baz)(]"); + + Assert.assertEquals(new String[] { "Foo", "Bar(Foo)" }, a1); + Assert.assertEquals(new String[] { "(Foo)", "Bar(Foo)", "Baz)(" }, a2); + } + @Test public void testParseCharacter() { Character[] a1 = ArraysParser.parseArray(Character[].class, "[a, b, c, 2, 9]"); @@ -133,8 +142,9 @@ public void testParseBigDecimal() { BigDecimal[] a1 = ArraysParser .parseArray(BigDecimal[].class, "[123456789123456789123456789.123456789123456789123456789, -123456789123456789123456789.123456789123456789123456789]"); - Assert.assertEquals(new BigDecimal[] { new BigDecimal("123456789123456789123456789.123456789123456789123456789"), new BigDecimal( - "-123456789123456789123456789.123456789123456789123456789") }, a1); + Assert.assertEquals(new BigDecimal[] { new BigDecimal( + "123456789123456789123456789.123456789123456789123456789"), new BigDecimal( + "-123456789123456789123456789.123456789123456789123456789") }, a1); } @Test diff --git a/store/src/test/java/org/gephi/graph/impl/BasicGraphStore.java b/src/test/java/org/gephi/graph/impl/BasicGraphStore.java similarity index 92% rename from store/src/test/java/org/gephi/graph/impl/BasicGraphStore.java rename to src/test/java/org/gephi/graph/impl/BasicGraphStore.java index c32fecd5..19cd34c1 100644 --- a/store/src/test/java/org/gephi/graph/impl/BasicGraphStore.java +++ b/src/test/java/org/gephi/graph/impl/BasicGraphStore.java @@ -30,22 +30,27 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; import org.gephi.graph.api.Column; import org.gephi.graph.api.ColumnIterable; import org.gephi.graph.api.DirectedGraph; import org.gephi.graph.api.Edge; import org.gephi.graph.api.EdgeIterable; import org.gephi.graph.api.Element; +import org.gephi.graph.api.GraphLock; import org.gephi.graph.api.GraphModel; import org.gephi.graph.api.GraphView; import org.gephi.graph.api.Interval; import org.gephi.graph.api.Node; import org.gephi.graph.api.NodeIterable; -import org.gephi.graph.api.SpatialContext; +import org.gephi.graph.api.SpatialIndex; import org.gephi.graph.api.Table; import org.gephi.graph.api.TextProperties; import org.gephi.graph.spi.LayoutData; @@ -169,6 +174,28 @@ public boolean removeAllNodes(Collection nodes) { return true; } + @Override + public boolean retainNodes(Collection nodes) { + Set nodeSet = new HashSet<>(nodes); + for (Node n : nodes) { + if (!nodeSet.contains(n)) { + removeNode(n); + } + } + return true; + } + + @Override + public boolean retainEdges(Collection edges) { + Set edgeSet = new HashSet<>(edges); + for (Edge e : edges) { + if (!edgeSet.contains(e)) { + removeEdge(e); + } + } + return true; + } + @Override public boolean contains(Node node) { return nodeStore.contains(node); @@ -184,6 +211,10 @@ public Node getNode(Object id) { return nodeStore.get(id); } + public Node getNodeByStoreId(int storeId) { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override public boolean hasNode(Object id) { return nodeStore.get(id) != null; @@ -194,6 +225,11 @@ public Edge getEdge(Object id) { return edgeStore.get(id); } + @Override + public Edge getEdgeByStoreId(int storeId) { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override public boolean hasEdge(Object id) { return edgeStore.get(id) != null; @@ -209,16 +245,21 @@ public EdgeIterable getEdges() { return new EdgeIterableWrapper(edgeStore.iterator()); } + @Override + public EdgeIterable getEdges(int type) { + return new EdgeIterableWrapper(edgeStore.typeIterator(type)); + } + @Override public NodeIterable getNeighbors(Node node) { - return new NodeIterableWrapper(new NeighborsUndirectedIterator((BasicNode) node, - edgeStore.inOutIterator((BasicNode) node))); + return new NodeIterableWrapper( + new NeighborsUndirectedIterator((BasicNode) node, edgeStore.inOutIterator((BasicNode) node))); } @Override public NodeIterable getNeighbors(Node node, int type) { - return new NodeIterableWrapper(new NeighborsUndirectedIterator((BasicNode) node, - edgeStore.inOutIterator((BasicNode) node, type))); + return new NodeIterableWrapper( + new NeighborsUndirectedIterator((BasicNode) node, edgeStore.inOutIterator((BasicNode) node, type))); } @Override @@ -298,8 +339,8 @@ public boolean isDirected(Edge edge) { @Override public boolean isIncident(Edge edge1, Edge edge2) { - return edge1.getSource() == edge2.getSource() || edge1.getTarget() == edge2.getTarget() || edge1.getSource() == edge2 - .getTarget() || edge1.getTarget() == edge2.getSource(); + return edge1.getSource() == edge2.getSource() || edge1.getTarget() == edge2.getTarget() || edge1 + .getSource() == edge2.getTarget() || edge1.getTarget() == edge2.getSource(); } @Override @@ -312,6 +353,11 @@ public GraphModel getModel() { return null; } + @Override + public int getVersion() { + return 0; + } + @Override public void clearEdges(Node node) { BasicNode basicNode = (BasicNode) node; @@ -368,6 +414,11 @@ public void writeLock() { public void writeUnlock() { } + @Override + public GraphLock getLock() { + return null; + } + @Override public EdgeIterable getSelfLoops() { throw new UnsupportedOperationException("Not supported yet."); @@ -687,6 +738,11 @@ public Interval[] getIntervals() { throw new UnsupportedOperationException("Not supported yet."); } + @Override + public Interval getTimeBounds() { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override public void setAttribute(Column column, Object value, Interval interval) { throw new UnsupportedOperationException("Not supported yet."); @@ -893,7 +949,7 @@ public static class BasicEdge extends BasicElement implements Edge { protected final BasicNode source; protected final BasicNode target; - protected final int type; + protected int type; protected final boolean directed; protected double weight; @@ -936,6 +992,11 @@ public int getType() { return type; } + @Override + public void setType(int type) { + this.type = type; + } + @Override public Object getTypeLabel() { throw new UnsupportedOperationException("Not supported yet."); @@ -951,6 +1012,11 @@ public boolean isDirected() { return directed; } + @Override + public boolean isMutual() { + return false; + } + public String getStringId() { return BasicEdgeStore.getStringId(source, target, directed); } @@ -1090,6 +1156,21 @@ public Iterator iterator() { return new BasicNodeIterator(idToNodeMap.values().iterator()); } + @Override + public Spliterator spliterator() { + return Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED | Spliterator.NONNULL); + } + + @Override + public Stream stream() { + return Collection.super.stream(); + } + + @Override + public Stream parallelStream() { + return Collection.super.parallelStream(); + } + @Override public Node[] toArray() { return idToNodeMap.values().toArray(new Node[0]); @@ -1105,6 +1186,11 @@ public Collection toCollection() { return Arrays.asList(idToNodeMap.values().toArray(new Node[0])); } + @Override + public Set toSet() { + return new HashSet<>(idToNodeMap.values()); + } + @Override public boolean add(Node node) { if (((BasicNode) node).getId() == null) { @@ -1259,6 +1345,11 @@ public Iterator iterator() { return new BasicEdgeIterator(idToEdgeMap.values().iterator()); } + public Iterator typeIterator(int type) { + ObjectSet set = new ObjectLinkedOpenHashSet<>(); + return new BasicEdgeIterator(idToEdgeMap.values().stream().filter(e -> e.type == type).iterator()); + } + public Iterator outIterator(BasicNode node) { ObjectSet set = new ObjectLinkedOpenHashSet<>(); for (Object2ObjectMap col : node.outEdges.values()) { @@ -1568,7 +1659,7 @@ public Iterator iterator() { @Override public Node[] toArray() { List list = new ArrayList<>(); - for (; iterator.hasNext();) { + while (iterator.hasNext()) { list.add(iterator.next()); } return list.toArray(new Node[0]); @@ -1577,12 +1668,21 @@ public Node[] toArray() { @Override public Collection toCollection() { List list = new ArrayList<>(); - for (; iterator.hasNext();) { + while (iterator.hasNext()) { list.add(iterator.next()); } return list; } + @Override + public Set toSet() { + Set set = new HashSet<>(); + while (iterator.hasNext()) { + set.add(iterator.next()); + } + return set; + } + @Override public void doBreak() { // Not used because no locking @@ -1605,7 +1705,7 @@ public Iterator iterator() { @Override public Edge[] toArray() { List list = new ArrayList<>(); - for (; iterator.hasNext();) { + while (iterator.hasNext()) { list.add(iterator.next()); } return list.toArray(new Edge[0]); @@ -1614,12 +1714,21 @@ public Edge[] toArray() { @Override public Collection toCollection() { List list = new ArrayList<>(); - for (; iterator.hasNext();) { + while (iterator.hasNext()) { list.add(iterator.next()); } return list; } + @Override + public Set toSet() { + Set set = new HashSet<>(); + while (iterator.hasNext()) { + set.add(iterator.next()); + } + return set; + } + @Override public void doBreak() { // Not used because no locking @@ -1627,7 +1736,7 @@ public void doBreak() { } @Override - public SpatialContext getSpatialContext() { - throw new UnsupportedOperationException("Not supported yet."); + public SpatialIndex getSpatialIndex() { + return null; } } diff --git a/store/src/test/java/org/gephi/graph/impl/ColumnImplTest.java b/src/test/java/org/gephi/graph/impl/ColumnImplTest.java similarity index 93% rename from store/src/test/java/org/gephi/graph/impl/ColumnImplTest.java rename to src/test/java/org/gephi/graph/impl/ColumnImplTest.java index aee2df15..549285e4 100644 --- a/store/src/test/java/org/gephi/graph/impl/ColumnImplTest.java +++ b/src/test/java/org/gephi/graph/impl/ColumnImplTest.java @@ -55,18 +55,23 @@ public void testColumnStandardizedType() { public void testColumnIsDynamic() { ColumnImpl col1 = new ColumnImpl("0", String.class, null, null, Origin.DATA, false, false); Assert.assertFalse(col1.isDynamic()); + Assert.assertFalse(col1.isDynamicAttribute()); ColumnImpl col2 = new ColumnImpl("0", TimestampDoubleMap.class, null, null, Origin.DATA, false, false); Assert.assertTrue(col2.isDynamic()); + Assert.assertTrue(col2.isDynamicAttribute()); ColumnImpl col3 = new ColumnImpl("0", IntervalDoubleMap.class, null, null, Origin.DATA, false, false); Assert.assertTrue(col3.isDynamic()); + Assert.assertTrue(col3.isDynamicAttribute()); ColumnImpl col4 = new ColumnImpl("0", IntervalSet.class, null, null, Origin.DATA, false, false); Assert.assertTrue(col4.isDynamic()); + Assert.assertFalse(col4.isDynamicAttribute()); ColumnImpl col5 = new ColumnImpl("0", TimestampSet.class, null, null, Origin.DATA, false, false); Assert.assertTrue(col5.isDynamic()); + Assert.assertFalse(col5.isDynamicAttribute()); } @Test @@ -148,4 +153,10 @@ public void testColumnDeepHashcode() { col2.setEstimator(Estimator.MIN); Assert.assertNotEquals(col1.deepHashCode(), col2.deepHashCode()); } + + @Test + public void testColumnDoesNotExist() { + ColumnImpl col1 = new ColumnImpl("0", int.class, null, null, Origin.DATA, false, false); + Assert.assertFalse(col1.exists()); + } } diff --git a/src/test/java/org/gephi/graph/impl/ColumnNoIndexTest.java b/src/test/java/org/gephi/graph/impl/ColumnNoIndexTest.java new file mode 100644 index 00000000..ebc893aa --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/ColumnNoIndexTest.java @@ -0,0 +1,257 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import org.gephi.graph.api.Column; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Element; +import org.gephi.graph.api.Estimator; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.Origin; +import org.gephi.graph.api.types.TimestampIntegerMap; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@Test(singleThreaded = true) +public class ColumnNoIndexTest { + + private GraphStore graphStore; + private ColumnNoIndexImpl fooIndex; + private ColumnNoIndexImpl ageIndex; + private ColumnNoIndexImpl priceIndex; + + @BeforeMethod + public void setup() { + graphStore = generateGraphStoreWithColumns(); + Column col = graphStore.nodeTable.getColumn("foo"); + Column col2 = graphStore.nodeTable.getColumn("age"); + Column col3 = graphStore.nodeTable.getColumn("price"); + + fooIndex = createIndex(graphStore, col.getId()); + ageIndex = createIndex(graphStore, col2.getId()); + priceIndex = createIndex(graphStore, col3.getId()); + } + + @AfterMethod + public void cleanUp() { + fooIndex = null; + ageIndex = null; + priceIndex = null; + graphStore = null; + } + + @Test + public void testEmpty() { + ColumnNoIndexImpl index = createIndex(graphStore, "id"); + + Assert.assertTrue(index.values().isEmpty()); + Assert.assertEquals(index.countValues(), 0); + Assert.assertEquals(index.countElements(), 0); + Assert.assertFalse(index.isSortable()); + Assert.assertEquals(index.count(null), 0); + Assert.assertEquals(index.count("foo"), 0); + Assert.assertFalse(index.get(null).iterator().hasNext()); + Assert.assertFalse(index.get("foo").iterator().hasNext()); + } + + @Test + public void testEmptyEdgeInstead() { + ColumnNoIndexImpl index = createEdgeIndex(graphStore, "id"); + + Assert.assertEquals(index.countElements(), 0); + Assert.assertTrue(index.values().isEmpty()); + Assert.assertFalse(index.get("foo").iterator().hasNext()); + } + + @Test + public void testCountsAdd() { + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "1", "bar"); + + Assert.assertEquals(fooIndex.countValues(), 1); + Assert.assertEquals(fooIndex.countElements(), 1); + + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "2", "bar"); + Assert.assertEquals(fooIndex.countValues(), 1); + Assert.assertEquals(fooIndex.countElements(), 2); + + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "3", "foo"); + Assert.assertEquals(fooIndex.countValues(), 2); + Assert.assertEquals(fooIndex.countElements(), 3); + } + + @Test + public void testCountsWithNulls() { + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "1", null); + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "2", null); + + Assert.assertEquals(fooIndex.countValues(), 1); + Assert.assertEquals(fooIndex.countElements(), 2); + } + + @Test + public void testCountsRemove() { + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "1", null); + graphStore.removeNode(graphStore.getNode("1")); + + Assert.assertEquals(fooIndex.countValues(), 0); + Assert.assertEquals(fooIndex.countElements(), 0); + } + + @Test + public void testCountByValue() { + Assert.assertEquals(fooIndex.count("bar"), 0); + Assert.assertEquals(fooIndex.count(null), 0); + + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "1", "bar"); + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "2", null); + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "3", "bar"); + + Assert.assertEquals(fooIndex.count("bar"), 2); + Assert.assertEquals(fooIndex.count(null), 1); + } + + @Test + public void testValues() { + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "1", "bar"); + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "2", null); + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "3", "bar"); + + Assert.assertEqualsNoOrder(fooIndex.values().toArray(new String[0]), new String[] { "bar", null }); + } + + @Test + public void testGet() { + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "1", "bar"); + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "2", null); + addNodeWithAttribute(graphStore, fooIndex.getColumn(), "3", "bar"); + + ArrayList res = new ArrayList<>(); + fooIndex.get("bar").forEach(res::add); + Assert.assertEqualsNoOrder(res + .toArray(new Node[0]), new Node[] { graphStore.getNode("1"), graphStore.getNode("3") }); + + ArrayList res2 = new ArrayList<>(); + fooIndex.get(null).forEach(res2::add); + Assert.assertEqualsNoOrder(res2.toArray(new Node[0]), new Node[] { graphStore.getNode("2") }); + } + + @Test + public void testIsSortable() { + Assert.assertFalse(fooIndex.isSortable()); + Assert.assertTrue(ageIndex.isSortable()); + } + + @Test + public void testGetMinMaxValueEmpty() { + Assert.assertNull(ageIndex.getMinValue()); + Assert.assertNull(ageIndex.getMaxValue()); + } + + @Test + public void testGetMinMaxValue() { + addNodeWithAttribute(graphStore, ageIndex.getColumn(), "1", 12); + addNodeWithAttribute(graphStore, ageIndex.getColumn(), "2", null); + addNodeWithAttribute(graphStore, ageIndex.getColumn(), "3", 6); + + Assert.assertEquals(ageIndex.getMinValue(), 6); + Assert.assertEquals(ageIndex.getMaxValue(), 12); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testGetMaxValueNotSortable() { + fooIndex.getMaxValue(); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testGetMinValueNotSortable() { + fooIndex.getMinValue(); + } + + @Test + public void testDynamicAttribute() { + TimestampIntegerMap t = new TimestampIntegerMap(); + t.put(2000.0, 100); + t.put(2010.0, 150); + + addNodeWithAttribute(graphStore, priceIndex.column, "1", t); + + Assert.assertTrue(priceIndex.isSortable()); + Assert.assertEquals(priceIndex.getMinValue(), 100); + Assert.assertEquals(priceIndex.getMaxValue(), 150); + Assert.assertEquals(priceIndex.values(), Collections.singletonList(100)); + Assert.assertEquals(priceIndex.count(100), 1); + Assert.assertEquals(priceIndex.countElements(), 1); + Assert.assertEquals(priceIndex.countValues(), 1); + } + + @Test + public void testDynamicAttributeWithEstimator() { + TimestampIntegerMap t = new TimestampIntegerMap(); + t.put(2000.0, 100); + t.put(2010.0, 150); + + addNodeWithAttribute(graphStore, priceIndex.column, "1", t); + priceIndex.column.setEstimator(Estimator.AVERAGE); + + Assert.assertEquals(priceIndex.getMinValue(), 100); + Assert.assertEquals(priceIndex.getMaxValue(), 150); + Assert.assertEquals(priceIndex.values(), Collections.singletonList(125.0)); + } + + @Test + public void testVersion() { + int version = fooIndex.getVersion(); + Node node = graphStore.factory.newNode("1"); + fooIndex.putValue(node, "bar"); + + Assert.assertTrue(fooIndex.getVersion() > version); + } + + private ColumnNoIndexImpl createIndex(GraphStore graphStore, String id) { + return new ColumnNoIndexImpl(graphStore.nodeTable.getColumn(id), graphStore, Node.class); + } + + private ColumnNoIndexImpl createEdgeIndex(GraphStore graphStore, String id) { + return new ColumnNoIndexImpl(graphStore.nodeTable.getColumn(id), graphStore, Edge.class); + } + + private Node addNodeWithAttribute(GraphStore store, Column column, String id, Object val) { + Node node = store.factory.newNode(id); + node.setAttribute(column, val); + store.addNode(node); + return node; + } + + private GraphStore generateGraphStoreWithColumns() { + GraphStore graphStore = new GraphStore(); + ColumnStore columnStore = graphStore.nodeTable.store; + columnStore.addColumn(new ColumnImpl(graphStore.nodeTable, "foo", String.class, "foo", null, Origin.DATA, false, + false)); + columnStore.addColumn(new ColumnImpl(graphStore.nodeTable, "age", Integer.class, "Age", null, Origin.DATA, true, + false)); + columnStore.addColumn(new ColumnImpl(graphStore.nodeTable, "price", TimestampIntegerMap.class, "Price", null, + Origin.DATA, true, false)); + + return graphStore; + } + +} diff --git a/store/src/test/java/org/gephi/graph/impl/ColumnObserverTest.java b/src/test/java/org/gephi/graph/impl/ColumnObserverTest.java similarity index 95% rename from store/src/test/java/org/gephi/graph/impl/ColumnObserverTest.java rename to src/test/java/org/gephi/graph/impl/ColumnObserverTest.java index 5595357b..89c12ba3 100644 --- a/store/src/test/java/org/gephi/graph/impl/ColumnObserverTest.java +++ b/src/test/java/org/gephi/graph/impl/ColumnObserverTest.java @@ -19,6 +19,7 @@ import org.gephi.graph.api.Column; import org.gephi.graph.api.ColumnDiff; import org.gephi.graph.api.ColumnObserver; +import org.gephi.graph.api.Configuration; import org.gephi.graph.api.Edge; import org.gephi.graph.api.Element; import org.gephi.graph.api.types.TimestampIntegerMap; @@ -45,6 +46,15 @@ public void testDefaultObserver() { Assert.assertFalse(observer.hasColumnChanged()); } + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testCreateObserverWhenDisabled() { + GraphStore store = new GraphStore(null, Configuration.builder().enableObservers(false).build()); + TableImpl table = store.nodeTable; + Column column = table.addColumn("0", Integer.class); + + column.createColumnObserver(false); + } + @Test(expectedExceptions = RuntimeException.class) public void testGetDiffWhenDisabled() { GraphStore store = new GraphStore(); @@ -255,7 +265,8 @@ public void testSetDynamicAttribute() { @Test public void testDestroyObserver() { - TableImpl table = new TableImpl(Node.class, false); + GraphStore store = new GraphStore(); + TableImpl table = store.nodeTable; Column column = table.addColumn("0", Integer.class); ColumnObserver observer = column.createColumnObserver(false); diff --git a/store/src/test/java/org/gephi/graph/impl/IndexImplTest.java b/src/test/java/org/gephi/graph/impl/ColumnStandardIndexTest.java similarity index 87% rename from store/src/test/java/org/gephi/graph/impl/IndexImplTest.java rename to src/test/java/org/gephi/graph/impl/ColumnStandardIndexTest.java index 9365ee8e..69877161 100644 --- a/store/src/test/java/org/gephi/graph/impl/IndexImplTest.java +++ b/src/test/java/org/gephi/graph/impl/ColumnStandardIndexTest.java @@ -26,102 +26,15 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Random; import java.util.Set; import org.gephi.graph.api.Column; -import org.gephi.graph.api.Origin; import org.gephi.graph.api.Node; +import org.gephi.graph.api.Origin; import org.testng.Assert; import org.testng.annotations.Test; -/** - * - * @author mbastian - */ -public class IndexImplTest { - - @Test - public void testIndexName() { - IndexImpl index = generateEmptyIndex(); - Assert.assertEquals(index.getIndexClass(), Node.class); - Assert.assertEquals(index.getIndexName(), "index_" + Node.class.getCanonicalName()); - } - - @Test - public void testAddColumn() { - ColumnStore columnStore = generateEmptyNodeStore(); - IndexImpl index = columnStore.indexStore.mainIndex; - ColumnImpl col = new ColumnImpl("foo", String.class, "foo", null, Origin.DATA, true, false); - col.setStoreId(0); - - Assert.assertEquals(index.size(), 0); - index.addColumn(col); - Assert.assertEquals(index.size(), 1); - Assert.assertSame(index.getIndex(col).column, col); - } - - @Test - public void testHasColumn() { - ColumnStore columnStore = generateEmptyNodeStore(); - IndexImpl index = columnStore.indexStore.mainIndex; - ColumnImpl col1 = new ColumnImpl("foo", String.class, "foo", null, Origin.DATA, true, false); - ColumnImpl col2 = new ColumnImpl("bar", String.class, "bar", null, Origin.DATA, false, false); - col1.setStoreId(0); - col2.setStoreId(1); - - Assert.assertFalse(index.hasColumn(col1)); - index.addColumn(col1); - index.addColumn(col2); - Assert.assertTrue(index.hasColumn(col1)); - Assert.assertFalse(index.hasColumn(col2)); - } - - @Test - public void testHasColumnDifferentIndex() { - ColumnStore columnStore1 = generateEmptyNodeStore(); - IndexImpl index1 = columnStore1.indexStore.mainIndex; - - ColumnStore columnStore2 = generateEmptyNodeStore(); - IndexImpl index2 = columnStore2.indexStore.mainIndex; - - ColumnImpl col1 = new ColumnImpl("foo", String.class, "foo", null, Origin.DATA, true, false); - ColumnImpl col2 = new ColumnImpl("bar", String.class, "bar", null, Origin.DATA, true, false); - col1.setStoreId(0); - col2.setStoreId(0); - - index1.addColumn(col1); - index2.addColumn(col2); - Assert.assertFalse(index1.hasColumn(col2)); - Assert.assertFalse(index2.hasColumn(col1)); - } - - @Test - public void testAddAllColumns() { - ColumnStore columnStore = generateEmptyNodeStore(); - IndexImpl index = columnStore.indexStore.mainIndex; - ColumnImpl col1 = new ColumnImpl("1", String.class, "1", null, Origin.DATA, true, false); - ColumnImpl col2 = new ColumnImpl("2", String.class, "2", null, Origin.DATA, false, false); - ColumnImpl col3 = new ColumnImpl("3", String.class, "3", null, Origin.DATA, true, false); - col1.setStoreId(0); - col2.setStoreId(1); - col3.setStoreId(2); - - index.addAllColumns(new ColumnImpl[] { col1, col2, col3 }); - Assert.assertEquals(index.size(), 2); - } - - @Test - public void testDestroy() { - ColumnStore columnStore = generateEmptyNodeStore(); - IndexImpl index = columnStore.indexStore.mainIndex; - ColumnImpl col = new ColumnImpl("1", String.class, "1", null, Origin.DATA, true, false); - col.setStoreId(0); - index.addColumn(col); - index.destroy(); - Assert.assertEquals(index.size(), 0); - Assert.assertNull(index.getIndex(col)); - } +public class ColumnStandardIndexTest { @Test public void testCount() { @@ -314,7 +227,7 @@ public void testWithNullDecorator() { index.put(fooColumn, null, n1); index.put(fooColumn, "bar", n3); - IndexImpl.AbstractIndex withNullIndex = index.getIndex("foo"); + ColumnIndexImpl withNullIndex = index.getIndex("foo"); Collection withNullCollection = withNullIndex.values(); Assert.assertEquals(withNullCollection.size(), 2); Assert.assertFalse(withNullCollection.isEmpty()); @@ -332,7 +245,7 @@ public void testWithNullDecorator() { Assert.assertEquals(withNullItr.next(), "bar"); Assert.assertFalse(withNullItr.hasNext()); - IndexImpl.AbstractIndex withoutNullIndex = index.getIndex("age"); + ColumnIndexImpl withoutNullIndex = index.getIndex("age"); Collection withoutNullCollection = withoutNullIndex.values(); Assert.assertEquals(withoutNullCollection.size(), 2); Assert.assertFalse(withoutNullCollection.isEmpty()); @@ -471,9 +384,9 @@ public void testGetNullEntry() { NodeImpl n = new NodeImpl(0); index.put("c", null, n); - Iterator>> itr = index.get(columnStore.getColumn("c")).iterator(); + Iterator>> itr = index.get(columnStore.getColumn("c")).iterator(); Assert.assertTrue(itr.hasNext()); - Entry> entry = itr.next(); + Map.Entry> entry = itr.next(); Assert.assertNull(entry.getKey()); Assert.assertEquals(entry.getValue().size(), 1); Assert.assertTrue(entry.getValue().contains(n)); @@ -488,9 +401,9 @@ public void testGetNullEntrySetValue() { NodeImpl n = new NodeImpl(0); index.put("c", null, n); - Iterator>> itr = index.get(columnStore.getColumn("c")).iterator(); + Iterator>> itr = index.get(columnStore.getColumn("c")).iterator(); Assert.assertTrue(itr.hasNext()); - Entry> entry = itr.next(); + Map.Entry> entry = itr.next(); entry.setValue(null); } @@ -664,6 +577,17 @@ public void testArrayTypes() { } } + @Test + public void testVersion() { + IndexImpl index = generateEmptyIndex(); + Column column = index.columnStore.getColumn("age"); + int version = index.getColumnIndex(column).getVersion(); + + NodeImpl n = new NodeImpl(0); + index.put(column, 12, n); + Assert.assertTrue(index.getColumnIndex(column).getVersion() > version); + } + // UTILITIES private NodeImpl[] generateNodesWithUniqueAttributes(IndexImpl index, boolean withNulls) { int count = 100; diff --git a/store/src/test/java/org/gephi/graph/impl/ColumnStoreTest.java b/src/test/java/org/gephi/graph/impl/ColumnStoreTest.java similarity index 96% rename from store/src/test/java/org/gephi/graph/impl/ColumnStoreTest.java rename to src/test/java/org/gephi/graph/impl/ColumnStoreTest.java index 91ad792a..8544b71b 100644 --- a/store/src/test/java/org/gephi/graph/impl/ColumnStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/ColumnStoreTest.java @@ -112,6 +112,11 @@ public boolean isDynamic() { throw new UnsupportedOperationException("Not supported yet."); } + @Override + public boolean isDynamicAttribute() { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override public boolean isNumber() { throw new UnsupportedOperationException("Not supported yet."); @@ -146,6 +151,11 @@ public Estimator getEstimator() { public void setEstimator(Estimator estimator) { throw new UnsupportedOperationException("Not supported yet."); } + + @Override + public boolean exists() { + throw new UnsupportedOperationException("Not supported yet."); + } }); } @@ -266,6 +276,19 @@ public void testHasColumn() { Assert.assertFalse(store.hasColumn("A")); } + @Test + public void testSizeByOrigin() { + ColumnStore store = new ColumnStore(Node.class, false); + ColumnImpl col1 = new ColumnImpl("0", Integer.class, null, null, Origin.DATA, false, false); + store.addColumn(col1); + + ColumnImpl col2 = new ColumnImpl("1", Integer.class, null, null, Origin.PROPERTY, false, false); + store.addColumn(col2); + + Assert.assertEquals(store.size(Origin.DATA), 1); + Assert.assertEquals(store.size(Origin.PROPERTY), 1); + } + @Test public void testHasColumnDifferentCase() { ColumnStore store = new ColumnStore(Node.class, false); @@ -287,18 +310,6 @@ public void testGetColumnKeys() { Assert.assertEquals(store.getColumnKeys(), set); } - @Test - public void testClear() { - ColumnStore store = new ColumnStore(Node.class, false); - ColumnImpl col = new ColumnImpl("0", Integer.class, null, null, Origin.DATA, false, false); - - store.addColumn(col); - store.clear(); - - Assert.assertFalse(store.hasColumn("0")); - Assert.assertEquals(store.size(), 0); - } - @Test public void testGarbage() { ColumnStore store = new ColumnStore(Node.class, false); diff --git a/store/src/test/java/org/gephi/graph/impl/ColumnVersionTest.java b/src/test/java/org/gephi/graph/impl/ColumnVersionTest.java similarity index 100% rename from store/src/test/java/org/gephi/graph/impl/ColumnVersionTest.java rename to src/test/java/org/gephi/graph/impl/ColumnVersionTest.java diff --git a/src/test/java/org/gephi/graph/impl/ConfigurationTest.java b/src/test/java/org/gephi/graph/impl/ConfigurationTest.java new file mode 100644 index 00000000..9222ed69 --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/ConfigurationTest.java @@ -0,0 +1,302 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import org.gephi.graph.api.Configuration; +import org.gephi.graph.api.TimeRepresentation; +import org.gephi.graph.api.types.IntervalDoubleMap; +import org.gephi.graph.api.types.TimestampDoubleMap; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ConfigurationTest { + + @Test + public void testDefaultBuilder() { + Configuration c = Configuration.builder().build(); + Assert.assertNotNull(c); + Assert.assertNotNull(c.getNodeIdType()); + Assert.assertEquals(c, Configuration.builder().build()); + Assert.assertEquals(c.hashCode(), Configuration.builder().build().hashCode()); + } + + @Test + public void testBuilderMultipleSet() { + Configuration.Builder b = Configuration.builder(); + Assert.assertEquals(b.build().getNodeIdType(), String.class); + b.nodeIdType(Integer.class); + Assert.assertEquals(b.build().getNodeIdType(), Integer.class); + } + + @Test + @SuppressWarnings("deprecation") + public void testDefaultDeprecated() { + Configuration c = new Configuration(); + Assert.assertNotNull(c.getNodeIdType()); + Assert.assertNotNull(c.getEdgeIdType()); + Assert.assertNotNull(c.getEdgeLabelType()); + Assert.assertNotNull(c.getEdgeWeightColumn()); + } + + @Test + public void testSetNodeIdType() { + Configuration c = Configuration.builder().nodeIdType(Float.class).build(); + Assert.assertEquals(c.getNodeIdType(), Float.class); + } + + @Test + @SuppressWarnings("deprecation") + public void testSetNodeIdTypeDeprecated() { + Configuration c = new Configuration(); + c.setNodeIdType(Float.class); + Assert.assertEquals(c.getNodeIdType(), Float.class); + } + + @Test + public void testSetEdgeIdType() { + Configuration c = Configuration.builder().edgeIdType(Float.class).build(); + Assert.assertEquals(c.getEdgeIdType(), Float.class); + } + + @Test + @SuppressWarnings("deprecation") + public void testSetEdgeIdTypeDeprecated() { + Configuration c = new Configuration(); + c.setEdgeIdType(Float.class); + Assert.assertEquals(c.getEdgeIdType(), Float.class); + } + + @Test + public void testSetEdgeLabelType() { + Configuration c = Configuration.builder().edgeLabelType(Float.class).build(); + Assert.assertEquals(c.getEdgeLabelType(), Float.class); + } + + @Test + @SuppressWarnings("deprecation") + public void testSetEdgeLabelTypeDeprecated() { + Configuration c = new Configuration(); + c.setEdgeLabelType(Float.class); + Assert.assertEquals(c.getEdgeLabelType(), Float.class); + } + + @Test + public void testSetEdgeWeightType() { + Configuration c = Configuration.builder().edgeWeightType(IntervalDoubleMap.class).build(); + Assert.assertEquals(c.getEdgeWeightType(), IntervalDoubleMap.class); + c = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); + Assert.assertEquals(c.getEdgeWeightType(), TimestampDoubleMap.class); + c = Configuration.builder().edgeWeightType(Double.class).build(); + Assert.assertEquals(c.getEdgeWeightType(), Double.class); + } + + @Test + @SuppressWarnings("deprecation") + public void testSetEdgeWeightTypeDeprecated() { + Configuration c = new Configuration(); + c.setEdgeWeightType(IntervalDoubleMap.class); + Assert.assertEquals(c.getEdgeWeightType(), IntervalDoubleMap.class); + c.setEdgeWeightType(TimestampDoubleMap.class); + Assert.assertEquals(c.getEdgeWeightType(), TimestampDoubleMap.class); + c.setEdgeWeightType(Double.class); + Assert.assertEquals(c.getEdgeWeightType(), Double.class); + } + + @Test + public void testSetTimeRepresentation() { + Configuration c = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); + Assert.assertEquals(c.getTimeRepresentation(), TimeRepresentation.INTERVAL); + } + + @Test + @SuppressWarnings("deprecation") + public void testSetTimeRepresentationDeprecated() { + Configuration c = new Configuration(); + c.setTimeRepresentation(TimeRepresentation.INTERVAL); + Assert.assertEquals(c.getTimeRepresentation(), TimeRepresentation.INTERVAL); + } + + @Test + public void testSetEdgeWeightColumn() { + Configuration c = Configuration.builder().edgeWeightColumn(false).build(); + Assert.assertEquals(c.getEdgeWeightColumn(), Boolean.FALSE); + } + + @Test + public void testDisableAutoLocking() { + Configuration c = Configuration.builder().enableAutoLocking(false).build(); + Assert.assertEquals(c.isEnableAutoLocking(), Boolean.FALSE); + } + + @Test + public void testDisableTimeIndexing() { + Configuration c = Configuration.builder().enableIndexTime(false).build(); + Assert.assertEquals(c.isEnableIndexTime(), Boolean.FALSE); + } + + @Test + @SuppressWarnings("deprecation") + public void testSetEdgeWeightColumnDeprecated() { + Configuration c = new Configuration(); + c.setEdgeWeightColumn(Boolean.FALSE); + Assert.assertEquals(c.getEdgeWeightColumn(), Boolean.FALSE); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testSetNodeIdTypeUnsupported() { + Configuration.builder().nodeIdType(int[].class).build(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testSetNodeIdTypeUnsupportedDeprecated() { + Configuration c = new Configuration(); + c.setNodeIdType(int[].class); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testSetEdgeIdTypeUnsupported() { + Configuration.builder().edgeIdType(int[].class).build(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + @SuppressWarnings("deprecation") + public void testSetEdgeIdTypeUnsupportedDeprecated() { + Configuration c = new Configuration(); + c.setEdgeIdType(int[].class); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testSetEdgeWeightTypeFloatUnsupported() { + Configuration.builder().edgeWeightType(Float.class).build(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + @SuppressWarnings("deprecation") + public void testSetEdgeWeightTypeFloatUnsupportedDeprecated() { + Configuration c = new Configuration(); + c.setEdgeWeightType(Float.class); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testSetEdgeWeightTypeNotNumberUnsupported() { + Configuration.builder().edgeWeightType(String.class).build(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + @SuppressWarnings("deprecation") + public void testSetEdgeWeightTypeNotNumberUnsupportedDeprecated() { + Configuration c = new Configuration(); + c.setEdgeWeightType(String.class); + } + + @Test + @SuppressWarnings("deprecation") + public void testDefaultEqualsDeprecated() { + Assert.assertEquals(new Configuration(), new Configuration()); + } + + @Test + @SuppressWarnings("deprecation") + public void testDefaultHashCodeDeprecated() { + Assert.assertEquals(new Configuration().hashCode(), new Configuration().hashCode()); + } + + @Test + public void testEquals() { + Configuration c1 = Configuration.builder().nodeIdType(Float.class).build(); + Configuration c2 = Configuration.builder().build(); + Assert.assertNotEquals(c2, c1); + } + + @Test + @SuppressWarnings("deprecation") + public void testEqualsDeprecated() { + Configuration c1 = new Configuration(); + Configuration c2 = new Configuration(); + Assert.assertEquals(c2, c1); + c2.setNodeIdType(Float.class); + Assert.assertNotEquals(c2, c1); + } + + @Test + public void testHashCode() { + Configuration c1 = Configuration.builder().nodeIdType(Float.class).build(); + Configuration c2 = Configuration.builder().build(); + Assert.assertNotEquals(c2.hashCode(), c1.hashCode()); + } + + @Test + @SuppressWarnings("deprecation") + public void testHashCodeDeprecated() { + Configuration c1 = new Configuration(); + Configuration c2 = new Configuration(); + Assert.assertEquals(c1.hashCode(), c2.hashCode()); + c2.setNodeIdType(Float.class); + Assert.assertNotEquals(c1.hashCode(), c2.hashCode()); + } + + @Test + public void testCopy() { + Configuration c1 = Configuration.builder().build(); + Configuration c2 = c1.copy(); + Assert.assertEquals(c2, c1); + Assert.assertNotSame(c2, c1); + + Configuration c3 = Configuration.builder().nodeIdType(Float.class).build(); + Assert.assertNotEquals(c3, c1); + Configuration c4 = c3.copy(); + Assert.assertEquals(c4, c3); + Assert.assertEquals(Float.class, c4.getNodeIdType()); + } + + @Test + @SuppressWarnings("deprecation") + public void testCopyDeprecated() { + Configuration c1 = new Configuration(); + Configuration c2 = c1.copy(); + Assert.assertEquals(c2, c1); + c1.setNodeIdType(Float.class); + Assert.assertNotEquals(c2.getNodeIdType(), Float.class); + Assert.assertNotEquals(c2, c1); + } + + @Test + public void testToConfiguration() { + Configuration c1 = Configuration.builder().nodeIdType(Float.class).build(); + Configuration c2 = new ConfigurationImpl(c1).toConfiguration(); + Assert.assertEquals(c1, c2); + } + + @Test(expectedExceptions = IllegalStateException.class) + public void testExceptionSpatialIndexWithDisabledNodeProperties() { + Configuration.builder().enableSpatialIndex(true).enableNodeProperties(false).build(); + } + + @Test + public void testToSting() { + Configuration c = Configuration.builder().build(); + Assert.assertNotNull(c.toString()); + Assert.assertTrue(c.toString().contains(c.getTimeRepresentation().name())); + } + + @Test + public void testDiffAsString() { + Configuration c1 = Configuration.builder().build(); + Configuration c2 = Configuration.builder().nodeIdType(Float.class).build(); + Assert.assertNotNull(c1.diffAsString(c2)); + Assert.assertEquals(c1.diffAsString(c2), "nodeIdType: class java.lang.String != class java.lang.Float"); + } +} diff --git a/src/test/java/org/gephi/graph/impl/DegreeNoIndexTest.java b/src/test/java/org/gephi/graph/impl/DegreeNoIndexTest.java new file mode 100644 index 00000000..8d5df62a --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/DegreeNoIndexTest.java @@ -0,0 +1,105 @@ +package org.gephi.graph.impl; + +import java.util.Collections; +import java.util.Iterator; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Graph; +import org.gephi.graph.api.Node; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class DegreeNoIndexTest { + + @Test + public void testEmpty() { + GraphStore store = GraphGenerator.generateEmptyGraphStore(); + DegreeNoIndexImpl index = new DegreeNoIndexImpl(store, DegreeNoIndexImpl.DegreeType.DEGREE); + Assert.assertEquals(index.countElements(), 0); + Assert.assertEquals(index.countValues(), 0); + Assert.assertEquals(index.count(0), 0); + Assert.assertFalse(index.get(0).iterator().hasNext()); + Assert.assertTrue(index.isSortable()); + Assert.assertSame(index.getColumn(), store.getModel().defaultColumns().degree()); + Assert.assertNull(index.getMinValue()); + Assert.assertNull(index.getMaxValue()); + Assert.assertTrue(index.values().isEmpty()); + } + + @Test + public void testOneNode() { + GraphStore store = new GraphStore(); + Node node = store.factory.newNode(); + store.addNode(node); + DegreeNoIndexImpl index = new DegreeNoIndexImpl(store, DegreeNoIndexImpl.DegreeType.DEGREE); + Assert.assertEquals(index.countElements(), 1); + Assert.assertEquals(index.countValues(), 1); + Assert.assertEquals(index.count(0), 1); + Assert.assertEquals(index.getMinValue().intValue(), 0); + Assert.assertEquals(index.getMaxValue().intValue(), 0); + } + + @Test + public void testSmallGraph() { + Graph graph = GraphGenerator.generateTinyGraphStore(); + Node node = graph.getModel().factory().newNode(); + graph.addNode(node); + + DegreeNoIndexImpl index = new DegreeNoIndexImpl(graph, DegreeNoIndexImpl.DegreeType.DEGREE); + Assert.assertEquals(index.countElements(), 3); + Assert.assertEquals(index.countValues(), 2); + Assert.assertEquals(index.count(1), 2); + Assert.assertEquals(index.getMinValue().intValue(), 0); + Assert.assertEquals(index.getMaxValue().intValue(), 1); + } + + @Test + public void testValues() { + Graph graph = GraphGenerator.generateTinyGraphStore(); + + DegreeNoIndexImpl index = new DegreeNoIndexImpl(graph, DegreeNoIndexImpl.DegreeType.DEGREE); + Assert.assertEquals(index.values(), Collections.singletonList(1)); + } + + @Test + public void testGetIterator() { + Graph graph = GraphGenerator.generateTinyGraphStore(); + + DegreeNoIndexImpl index = new DegreeNoIndexImpl(graph, DegreeNoIndexImpl.DegreeType.DEGREE); + Iterator itr = index.get(1).iterator(); + Assert.assertTrue(itr.hasNext()); + Assert.assertEquals(itr.next(), graph.getNode("1")); + Assert.assertTrue(itr.hasNext()); + Assert.assertEquals(itr.next(), graph.getNode("2")); + Assert.assertFalse(itr.hasNext()); + } + + @Test + public void testInDegree() { + Graph graph = GraphGenerator.generateTinyGraphStore(); + Edge edge = graph.getEdge("0"); + + DegreeNoIndexImpl index = new DegreeNoIndexImpl(graph, DegreeNoIndexImpl.DegreeType.IN_DEGREE); + index.get(1).iterator().forEachRemaining(n -> Assert.assertSame(n, edge.getTarget())); + index.get(0).iterator().forEachRemaining(n -> Assert.assertSame(n, edge.getSource())); + } + + @Test + public void testOutDegree() { + Graph graph = GraphGenerator.generateTinyGraphStore(); + Edge edge = graph.getEdge("0"); + + DegreeNoIndexImpl index = new DegreeNoIndexImpl(graph, DegreeNoIndexImpl.DegreeType.OUT_DEGREE); + index.get(0).iterator().forEachRemaining(n -> Assert.assertSame(n, edge.getTarget())); + index.get(1).iterator().forEachRemaining(n -> Assert.assertSame(n, edge.getSource())); + } + + @Test + public void testVersion() { + Graph graph = GraphGenerator.generateTinyGraphStore(); + + DegreeNoIndexImpl index = new DegreeNoIndexImpl(graph, DegreeNoIndexImpl.DegreeType.DEGREE); + int version = index.getVersion(); + graph.removeNode(graph.getNode("1")); + Assert.assertNotEquals(index.getVersion(), version); + } +} diff --git a/store/src/test/java/org/gephi/graph/impl/EdgeImplTest.java b/src/test/java/org/gephi/graph/impl/EdgeImplTest.java similarity index 65% rename from store/src/test/java/org/gephi/graph/impl/EdgeImplTest.java rename to src/test/java/org/gephi/graph/impl/EdgeImplTest.java index d29d2978..12d5d483 100644 --- a/store/src/test/java/org/gephi/graph/impl/EdgeImplTest.java +++ b/src/test/java/org/gephi/graph/impl/EdgeImplTest.java @@ -44,10 +44,17 @@ public void testSetGetWeight() { Assert.assertEquals(e.getWeight(), 42.0); } + @Test + public void testZeroWeight() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + Edge e = graphStore.getEdge("0"); + e.setWeight(0.0); + Assert.assertEquals(e.getWeight(), 0.0); + } + @Test public void testGetDefaultTimestampWeight() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); e.setWeight(42.0, 1.0); @@ -56,27 +63,61 @@ public void testGetDefaultTimestampWeight() { @Test public void testGetDefaultTimestampWeightWhenNotSet() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); Assert.assertEquals(e.getWeight(2.0), GraphStoreConfiguration.DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING); } + @Test + public void testGetDefaultIntervalWeight() { + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL) + .edgeWeightType(IntervalDoubleMap.class).build(); + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); + Edge e = graphStore.getEdge("0"); + e.setWeight(42.0, new Interval(1.0, 2.0)); + Assert.assertEquals(e + .getWeight(new Interval(2.1, 4.0)), GraphStoreConfiguration.DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING); + } + @Test public void testGetDefaultIntervalWeightWhenNotSet() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); - config.setEdgeWeightType(IntervalDoubleMap.class); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL) + .edgeWeightType(IntervalDoubleMap.class).build(); + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); + Edge e = graphStore.getEdge("0"); + Assert.assertEquals(e + .getWeight(new Interval(2.0, 4.0)), GraphStoreConfiguration.DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING); + } + + @Test + public void testGetWeightInterval() { + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL) + .edgeWeightType(IntervalDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); - Assert.assertEquals(e.getWeight(new Interval(2.0, 4.0)), GraphStoreConfiguration.DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING); + e.setWeight(42.0, new Interval(1.0, 2.0)); + Assert.assertEquals(e.getWeight(new Interval(2.0, 4.0)), 42.0); + } + + @Test + public void testGetWeightIntervalMax() { + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL) + .edgeWeightType(IntervalDoubleMap.class).build(); + + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); + Column col = graphStore.edgeTable.store.getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + col.setEstimator(Estimator.MAX); + + Edge e = graphStore.getEdge("0"); + e.setWeight(10.0, new Interval(1.0, 2.0)); + e.setWeight(20.0, new Interval(2.0, 3.0)); + Assert.assertEquals(e.getWeight(new Interval(2.0, 2.0)), 20.0); } @Test public void testSetTimestampWeight() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); e.setWeight(42.0, 1.0); @@ -88,9 +129,8 @@ public void testSetTimestampWeight() { @Test public void testSetIntervalWeight() { - Configuration config = new Configuration(); - config.setEdgeWeightType(IntervalDoubleMap.class); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL) + .edgeWeightType(IntervalDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); Interval i1 = new Interval(1.0, 2.0); @@ -104,9 +144,8 @@ public void testSetIntervalWeight() { @Test public void testIntervalWeightUsesFirstValueInOverlappingIntervalsEstimator() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); - config.setEdgeWeightType(IntervalDoubleMap.class); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL) + .edgeWeightType(IntervalDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); e.setWeight(42.0, new Interval(1.0, 2.0)); @@ -129,8 +168,7 @@ public void testGetWeightIntervalError() { @Test public void testGetWeightStaticError() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); e.setWeight(1.0, 1.0); @@ -151,8 +189,7 @@ public void testHasDynamicWeightDouble() { @Test public void testHasDynamicWeightTimestamp() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); Assert.assertTrue(e.hasDynamicWeight()); @@ -162,9 +199,8 @@ public void testHasDynamicWeightTimestamp() { @Test public void testHasDynamicWeightInterval() { - Configuration config = new Configuration(); - config.setEdgeWeightType(IntervalDoubleMap.class); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL) + .edgeWeightType(IntervalDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); Assert.assertTrue(e.hasDynamicWeight()); @@ -174,17 +210,16 @@ public void testHasDynamicWeightInterval() { @Test public void testGetDefaultWeightByGraphView() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); - Assert.assertEquals(e.getWeight(graphStore.mainGraphView), GraphStoreConfiguration.DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING); + Assert.assertEquals(e + .getWeight(graphStore.mainGraphView), GraphStoreConfiguration.DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING); } @Test public void testGetTimestampWeightMainGraphView() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); e.setWeight(42.0, 1.0); @@ -193,32 +228,36 @@ public void testGetTimestampWeightMainGraphView() { Assert.assertEquals(e.getWeight(graphStore.getView()), 10.0); } - // @Test - // public void testGetIntervalWeightMainGraphView() { - // GraphStore graphStore = - // GraphGenerator.generateTinyGraphStore(TimeRepresentation.INTERVAL); - // Edge e = graphStore.getEdge("0"); - // Interval i1 = new Interval(1.0, 2.0); - // Interval i2 = new Interval(3.0, 4.0); - // e.setWeight(42.0, i1); - // Assert.assertEquals(e.getWeight(graphStore.getView()), 42.0); - // e.setWeight(10.0, i2); - // Assert.assertEquals(e.getWeight(graphStore.getView()), 10.0); - // } + @Test + public void testGetWeightGraphViewMax() { + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL) + .edgeWeightType(IntervalDoubleMap.class).build(); + + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); + Column col = graphStore.edgeTable.store.getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + col.setEstimator(Estimator.MAX); + + Edge e = graphStore.getEdge("0"); + Interval i1 = new Interval(1.0, 2.0); + Interval i2 = new Interval(3.0, 4.0); + e.setWeight(10.0, i1); + e.setWeight(20.0, i2); + Assert.assertEquals(e.getWeight(graphStore.getView()), 20.0); + } + @Test public void testGetWeightNoValue() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); e.setAttribute("weight", null); - Assert.assertEquals(e.getWeight(graphStore.getView()), GraphStoreConfiguration.DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING); + Assert.assertEquals(e + .getWeight(graphStore.getView()), GraphStoreConfiguration.DEFAULT_DYNAMIC_EDGE_WEIGHT_WHEN_MISSING); } @Test public void testGetWeightDefaultEstimator() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); e.setWeight(10.0, 1.0); @@ -228,8 +267,7 @@ public void testGetWeightDefaultEstimator() { @Test public void testGetWeightAverageEstimator() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); Column col = graphStore.edgeTable.store.getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); @@ -241,8 +279,7 @@ public void testGetWeightAverageEstimator() { @Test public void testGetWeightWithView() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); e.setWeight(10.0, 1.0); @@ -254,10 +291,20 @@ public void testGetWeightWithView() { Assert.assertEquals(e.getWeight(view), 20.0); } + @Test + public void testGetWeightWithViewStatic() { + Configuration config = Configuration.builder().edgeWeightType(Double.class).build(); + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); + Edge e = graphStore.getEdge("0"); + e.setWeight(10.0); + GraphViewImpl view = graphStore.viewStore.createView(); + + Assert.assertEquals(e.getWeight(view), 10.0); + } + @Test public void testGetWeightsTimestamp() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); e.setWeight(10.0, 3.0); @@ -278,9 +325,8 @@ public void testGetWeightsTimestamp() { @Test public void testGetWeightsInterval() { - Configuration config = new Configuration(); - config.setEdgeWeightType(IntervalDoubleMap.class); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL) + .edgeWeightType(IntervalDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); e.setWeight(10.0, new Interval(3.0, 4.0)); @@ -309,8 +355,7 @@ public void testGetWeightsStatic() { @Test public void testSetAttributeWeightTimestamp() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore graphStore = GraphGenerator.generateTinyGraphStore(config); Edge e = graphStore.getEdge("0"); Column col = graphStore.edgeTable.getColumn(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); @@ -334,7 +379,7 @@ public void testGetTypeLabelDefault() { @Test public void testGetTypeLabelCustom() { - GraphStore graphStore = new GraphStore(null); + GraphStore graphStore = new GraphStore(); EdgeTypeStore edgeTypeStore = graphStore.edgeTypeStore; int typeId = edgeTypeStore.addType("foo"); @@ -381,4 +426,21 @@ public void testGetTable() { Assert.assertSame(e.getTable(), graphStore.getModel().getEdgeTable()); } } + + @Test + public void testProperties() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + Edge e = graphStore.getEdge("0"); + Assert.assertNotNull(e.getTextProperties()); + Assert.assertNotNull(e.getColor()); + Assert.assertEquals(e.alpha(), 1f); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testPropertiesDisabled() { + GraphStore graphStore = GraphGenerator + .generateTinyGraphStore(Configuration.builder().enableEdgeProperties(false).build()); + Edge e = graphStore.getEdge("0"); + Assert.assertNull(e.getColor()); + } } diff --git a/store/src/test/java/org/gephi/graph/impl/EdgeStoreTest.java b/src/test/java/org/gephi/graph/impl/EdgeStoreTest.java similarity index 79% rename from store/src/test/java/org/gephi/graph/impl/EdgeStoreTest.java rename to src/test/java/org/gephi/graph/impl/EdgeStoreTest.java index 2dc5fc31..2c1a313e 100644 --- a/store/src/test/java/org/gephi/graph/impl/EdgeStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/EdgeStoreTest.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.gephi.graph.impl; import it.unimi.dsi.fastutil.ints.Int2IntMap; @@ -29,17 +30,22 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.Set; +import java.util.Spliterator; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +import org.gephi.graph.api.Configuration; import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Node; import org.testng.Assert; import org.testng.annotations.Test; /** - * * @author mbastian */ public class EdgeStoreTest { @@ -363,6 +369,14 @@ public void testContainsAll() { Assert.assertTrue(edgeStore.containsAll(Arrays.asList(edges))); } + @Test + public void testContainsAllEmpty() { + EdgeStore edgeStore = new EdgeStore(); + EdgeImpl[] edges = GraphGenerator.generateEdgeList(3); + edgeStore.addAll(Arrays.asList(edges)); + Assert.assertTrue(edgeStore.containsAll(new java.util.ArrayList<>())); + } + @Test public void testIterator() { EdgeStore edgeStore = new EdgeStore(); @@ -607,10 +621,43 @@ public void testParallel() { edges[i] = e; Assert.assertTrue(edgeStore.add(e)); Assert.assertTrue(edgeStore.contains(e)); + Assert.assertEquals(edgeStore.size(), i + 1); + Assert.assertEquals(edgeStore.size(0), i + 1); + } + for (int i = 0; i < 5; i++) { + Assert.assertTrue(edgeStore.remove(edges[i])); + Assert.assertFalse(edgeStore.contains(edges[i])); + Assert.assertEquals(edgeStore.size(), 9 - i); + Assert.assertEquals(edgeStore.size(0), 9 - i); + } + for (int i = 5; i < 10; i++) { + Assert.assertTrue(edgeStore.contains(edges[i])); + } + } + + @Test + public void testParallelUndirected() { + NodeStore nodeStore = new NodeStore(); + NodeImpl n1 = new NodeImpl("0"); + NodeImpl n2 = new NodeImpl("1"); + nodeStore.add(n1); + nodeStore.add(n2); + + EdgeStore edgeStore = new EdgeStore(); + EdgeImpl[] edges = new EdgeImpl[10]; + for (int i = 0; i < 10; i++) { + EdgeImpl e = new EdgeImpl("" + i, n1, n2, 0, 1.0, false); + edges[i] = e; + Assert.assertTrue(edgeStore.add(e)); + Assert.assertTrue(edgeStore.contains(e)); + Assert.assertEquals(edgeStore.size(), i + 1); + Assert.assertEquals(edgeStore.size(0), i + 1); } for (int i = 0; i < 5; i++) { Assert.assertTrue(edgeStore.remove(edges[i])); Assert.assertFalse(edgeStore.contains(edges[i])); + Assert.assertEquals(edgeStore.size(), 9 - i); + Assert.assertEquals(edgeStore.size(0), 9 - i); } for (int i = 5; i < 10; i++) { Assert.assertTrue(edgeStore.contains(edges[i])); @@ -646,6 +693,24 @@ public void testRemoveMultitypes() { Assert.assertEquals(edgeStore.size(1), 0); } + @Test + public void testRemoveMultitypesReverseOrder() { + NodeStore nodeStore = new NodeStore(); + NodeImpl n1 = new NodeImpl("0"); + NodeImpl n2 = new NodeImpl("1"); + nodeStore.add(n1); + nodeStore.add(n2); + + EdgeImpl e1 = new EdgeImpl("0", n1, n2, 0, 1.0, true); + EdgeImpl e2 = new EdgeImpl("1", n1, n2, 1, 1.0, true); + EdgeStore edgeStore = new EdgeStore(); + edgeStore.add(e1); + edgeStore.add(e2); + + edgeStore.remove(e2); + edgeStore.remove(e1); + } + @Test public void testOutIterator() { EdgeImpl[] edges = GraphGenerator.generateSmallEdgeList(); @@ -802,7 +867,7 @@ public void testInOutIterator() { Object2ObjectMap outEdgeMap = getObjectMap(edges); for (NodeImpl n : getNodes(edges)) { - EdgeStore.EdgeInOutIterator itr = edgeStore.edgeIterator(n); + EdgeStore.EdgeInOutIterator itr = edgeStore.edgeIterator(n, true); for (; itr.hasNext();) { EdgeImpl e = itr.next(); if (e.isSelfLoop()) { @@ -824,13 +889,13 @@ public void testInOutIterator() { @Test(expectedExceptions = NullPointerException.class) public void testInOutIteratorNull() { EdgeStore edgeStore = new EdgeStore(); - edgeStore.edgeIterator(null); + edgeStore.edgeIterator((Node) null, true); } @Test(expectedExceptions = IllegalArgumentException.class) public void testInOutIteratorInvalid() { EdgeStore edgeStore = new EdgeStore(); - edgeStore.edgeIterator(new NodeImpl("0")); + edgeStore.edgeIterator(new NodeImpl("0"), true); } @Test @@ -846,7 +911,7 @@ public void testInOutIteratorAfterRemove() { Object2ObjectMap outEdgeMap = getObjectMap(edgeList.toArray(new EdgeImpl[0])); for (NodeImpl n : getNodes(edges)) { - EdgeStore.EdgeInOutIterator itr = edgeStore.edgeIterator(n); + EdgeStore.EdgeInOutIterator itr = edgeStore.edgeIterator(n, true); for (; itr.hasNext();) { EdgeImpl e = itr.next(); if (e.isSelfLoop()) { @@ -873,7 +938,7 @@ public void testInOutIteratorRemove() { int index = 0; for (NodeImpl n : getNodes(edges)) { - EdgeStore.EdgeInOutIterator itr = edgeStore.edgeIterator(n); + EdgeStore.EdgeInOutIterator itr = edgeStore.edgeIterator(n, true); for (; itr.hasNext();) { EdgeImpl e = itr.next(); itr.remove(); @@ -885,6 +950,80 @@ public void testInOutIteratorRemove() { testContainsNone(edgeStore, Arrays.asList(edges)); } + @Test + public void testEdgeInOutMultiIterator() { + EdgeStore edgeStore = new EdgeStore(); + EdgeImpl[] edges = GraphGenerator.generateSmallEdgeList(); + edgeStore.addAll(Arrays.asList(edges)); + + // Get all nodes from the edges + List nodeList = Arrays.asList(getNodes(edges)); + + // Collect edges using multi-iterator + EdgeStore.EdgeInOutMultiIterator multiIterator = edgeStore.edgeIterator(nodeList.iterator(), true); + Set multiIteratorEdges = new ObjectOpenHashSet<>(); + while (multiIterator.hasNext()) { + EdgeImpl edge = multiIterator.next(); + multiIteratorEdges.add(edge); + } + + // Collect edges using individual iterators (old approach) + Set individualIteratorEdges = new ObjectOpenHashSet<>(); + for (NodeImpl node : nodeList) { + EdgeStore.EdgeInOutIterator singleIterator = edgeStore.edgeIterator(node, true); + while (singleIterator.hasNext()) { + EdgeImpl edge = singleIterator.next(); + individualIteratorEdges.add(edge); + } + } + + // Both approaches should yield the same set of edges + Assert.assertEquals(multiIteratorEdges, individualIteratorEdges); + + // Verify all edges are accounted for + Set allEdges = new ObjectOpenHashSet<>(Arrays.asList(edges)); + Assert.assertEquals(multiIteratorEdges, allEdges); + } + + @Test + public void testEdgeInOutMultiIteratorEmpty() { + EdgeStore edgeStore = new EdgeStore(); + List emptyNodeList = new ArrayList<>(); + + EdgeStore.EdgeInOutMultiIterator multiIterator = edgeStore.edgeIterator(emptyNodeList.iterator(), true); + Assert.assertFalse(multiIterator.hasNext()); + } + + @Test + public void testEdgeInOutMultiIteratorRemove() { + EdgeStore edgeStore = new EdgeStore(); + EdgeImpl[] edges = GraphGenerator.generateSmallEdgeList(); + edgeStore.addAll(Arrays.asList(edges)); + + List nodeList = Arrays.asList(getNodes(edges)); + EdgeStore.EdgeInOutMultiIterator multiIterator = edgeStore.edgeIterator(nodeList.iterator(), true); + + int initialSize = edgeStore.size(); + int removedCount = 0; + + while (multiIterator.hasNext()) { + EdgeImpl edge = multiIterator.next(); + multiIterator.remove(); + removedCount++; + Assert.assertFalse(edgeStore.contains(edge)); + Assert.assertEquals(edgeStore.size(), initialSize - removedCount); + } + + Assert.assertEquals(removedCount, initialSize); + Assert.assertTrue(edgeStore.isEmpty()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testEdgeInOutMultiIteratorNullIterator() { + EdgeStore edgeStore = new EdgeStore(); + edgeStore.edgeIterator((Iterator) null, true); + } + @Test public void testOutTypeIterator() { EdgeImpl[] edges = GraphGenerator.generateSmallMultiTypeEdgeList(); @@ -1249,6 +1388,8 @@ public void testMutualParallel() { Assert.assertTrue(edgeStore.add(e1)); Assert.assertFalse(e1.isMutual()); Assert.assertTrue(edgeStore.add(e2)); + Assert.assertEquals(edgeStore.size(), 2); + Assert.assertEquals(edgeStore.size(0), 2); Assert.assertTrue(edgeStore.add(e3)); Assert.assertTrue(e1.isMutual()); @@ -1258,6 +1399,8 @@ public void testMutualParallel() { Assert.assertEquals(n1.getUndirectedDegree(), 2); Assert.assertEquals(n2.getDegree(), 3); Assert.assertEquals(n2.getUndirectedDegree(), 2); + Assert.assertEquals(edgeStore.size(), 3); + Assert.assertEquals(edgeStore.size(0), 3); Assert.assertTrue(edgeStore.add(e4)); Assert.assertTrue(e4.isMutual()); @@ -1266,18 +1409,35 @@ public void testMutualParallel() { Assert.assertEquals(n1.getUndirectedDegree(), 2); Assert.assertEquals(n2.getDegree(), 4); Assert.assertEquals(n2.getUndirectedDegree(), 2); + Assert.assertEquals(edgeStore.size(), 4); + Assert.assertEquals(edgeStore.size(0), 4); Assert.assertTrue(edgeStore.remove(e1)); Assert.assertEquals(n1.getDegree(), 3); Assert.assertEquals(n1.getUndirectedDegree(), 2); Assert.assertEquals(n2.getDegree(), 3); Assert.assertEquals(n2.getUndirectedDegree(), 2); + Assert.assertEquals(edgeStore.size(), 3); + Assert.assertEquals(edgeStore.size(0), 3); Assert.assertTrue(edgeStore.remove(e2)); Assert.assertEquals(n1.getDegree(), 2); Assert.assertEquals(n1.getUndirectedDegree(), 2); Assert.assertEquals(n2.getDegree(), 2); Assert.assertEquals(n2.getUndirectedDegree(), 2); + Assert.assertEquals(edgeStore.size(), 2); + Assert.assertEquals(edgeStore.size(0), 2); + } + + @Test + public void testRemoveMutualEdge() { + EdgeImpl[] edges = GraphGenerator.generateMutualEdges(1); + EdgeTypeStore edgeTypeStore = new EdgeTypeStore(); + EdgeStore edgeStore = new EdgeStore(edgeTypeStore, null, null, null, null, null); + edgeStore.addAll(Arrays.asList(edges)); + edgeStore.remove(edges[0]); + Assert.assertFalse(edges[0].isMutual()); + Assert.assertFalse(edges[1].isMutual()); } @Test @@ -1570,9 +1730,9 @@ public void testNodeAdjacentAllTypes() { for (EdgeImpl edge : edges) { Assert.assertTrue(edgeStore.isAdjacent(edge.source, edge.target)); - if (!edge.isSelfLoop() && !edge.isMutual()) { + if (!edge.isSelfLoop() && !edgeStore.containsAnyType(edge.target, edge.source)) { Assert.assertFalse(edgeStore.isAdjacent(edge.target, edge.source)); - } else if (edge.isMutual()) { + } else if (edgeStore.containsAnyType(edge.target, edge.source)) { Assert.assertTrue(edgeStore.isAdjacent(edge.target, edge.source)); } } @@ -1623,7 +1783,7 @@ public void testEdgeIncident() { @Test public void testTypeCounting() { EdgeTypeStore edgeTypeStore = new EdgeTypeStore(); - EdgeStore edgeStore = new EdgeStore(edgeTypeStore, null, null, null, null); + EdgeStore edgeStore = new EdgeStore(edgeTypeStore, null, null, null, null, null); EdgeImpl[] edges = GraphGenerator.generateSmallMultiTypeEdgeList(); Int2IntMap counts = new Int2IntOpenHashMap(); @@ -1710,7 +1870,7 @@ public void testUndirectedIterator() { } EdgeStore.EdgeStoreIterator undirectedIterator = edgeStore.iteratorUndirected(); - for (; undirectedIterator.hasNext();) { + while (undirectedIterator.hasNext()) { EdgeImpl e = undirectedIterator.next(); Assert.assertTrue(edgeSet.remove(e)); } @@ -1718,6 +1878,176 @@ public void testUndirectedIterator() { Assert.assertEquals(0, edgeSet.size()); } + @Test + public void testUndirectedSpliterator() { + EdgeImpl[] edges = GraphGenerator.generateMutualEdges(0); + EdgeStore edgeStore = new EdgeStore(); + edgeStore.addAll(Arrays.asList(edges)); + + // Collect ids from spliteratorUndirected + Spliterator spliterator = edgeStore.spliteratorUndirected(); + Assert.assertEquals(spliterator.estimateSize(), 1); + List edgeList = StreamSupport.stream(spliterator, true).collect(Collectors.toList()); + Assert.assertEquals(edgeList.size(), 1); + Assert.assertEquals(edgeList.get(0), edges[0]); + } + + @Test + public void testUndirectedSpliteratorMatchesIterator() { + EdgeImpl[] edges = GraphGenerator.generateEdgeList(300, 0, true, true, false); + EdgeStore edgeStore = new EdgeStore(); + edgeStore.addAll(Arrays.asList(edges)); + + // Collect ids from iteratorUndirected + List idsIter = iteratorToArray(edgeStore.iteratorUndirected()); + + // Collect ids from spliteratorUndirected + Spliterator spliterator = edgeStore.spliteratorUndirected(); + Assert.assertEquals(spliterator.estimateSize(), idsIter.size()); + List idsSplit = StreamSupport.stream(spliterator, true).collect(Collectors.toList()); + + Assert.assertEquals(idsSplit, idsIter); + } + + @Test + public void testTypeSpliteratorMatchesIterator() { + EdgeImpl[] edges = GraphGenerator.generateSmallMultiTypeEdgeList(); + EdgeStore edgeStore = new EdgeStore(); + edgeStore.addAll(Arrays.asList(edges)); + + for (boolean directed : new boolean[] { true, false }) { + for (int type = 0; type < 3; type++) { + // Collect ids from iteratorUndirected + List idsIter = iteratorToArray(edgeStore.iteratorType(type, directed)); + + // Collect ids from spliteratorUndirected + Spliterator spliterator = edgeStore.spliteratorType(type, directed); + Assert.assertEquals(spliterator.estimateSize(), idsIter.size()); + List idsSplit = StreamSupport.stream(spliterator, true).collect(Collectors.toList()); + + Assert.assertEquals(idsSplit, idsIter); + } + } + } + + @Test + public void testSelfLoopSpliterator() { + EdgeStore edgeStore = new EdgeStore(); + edgeStore.add(GraphGenerator.generateSelfLoop(0, true)); + + List idsSplit = StreamSupport.stream(edgeStore.spliteratorSelfLoop(), true).collect(Collectors.toList()); + List idsIter = iteratorToArray(edgeStore.iteratorSelfLoop()); + Assert.assertEquals(idsSplit, idsIter); + } + + @Test + public void testFilteredSpliteratorCharacteristicsAndEstimate() { + EdgeImpl[] edges = GraphGenerator.generateEdgeList(200, 0, true, true, false); + EdgeStore edgeStore = new EdgeStore(); + edgeStore.addAll(Arrays.asList(edges)); + + Spliterator sp = edgeStore.spliteratorUndirected(); + int ch = sp.characteristics(); + Assert.assertTrue((ch & Spliterator.SIZED) != 0); + Assert.assertTrue((ch & Spliterator.SUBSIZED) == 0); + + // Count expected + List expected = iteratorToArray(edgeStore.iteratorUndirected()); + Assert.assertEquals(sp.estimateSize(), expected.size()); + + // Consume 5 and check estimate decreases + for (int i = 0; i < 5; i++) { + Assert.assertTrue(sp.tryAdvance(e -> { + })); + } + Assert.assertEquals(sp.estimateSize(), expected.size() - 5); + } + + @Test + public void testFilteredSpliteratorParallelCollectAcrossBlocks() { + // Regression: FilteredSizedEdgeSpliterator used to keep SIZED after splitting, with a + // per-half totalSize derived from the unfiltered range count. Parallel collect would + // then fail with "Accept exceeded fixed size of N" inside FixedNodeBuilder. + int undirectedCount = GraphStoreConfiguration.EDGESTORE_BLOCK_SIZE + (GraphStoreConfiguration.EDGESTORE_BLOCK_SIZE / 2); + EdgeImpl[] edges = GraphGenerator.generateEdgeList(undirectedCount, 0, false, true, false); + EdgeStore edgeStore = new EdgeStore(); + edgeStore.addAll(Arrays.asList(edges)); + + int expectedUndirected = edgeStore.undirectedSize(); + Assert.assertTrue(edgeStore.blocksCount > 1, "Need multiple blocks to exercise trySplit"); + + List collected = StreamSupport.stream(edgeStore.spliteratorUndirected(), true) + .collect(Collectors.toList()); + Assert.assertEquals(collected.size(), expectedUndirected); + Assert.assertEquals(new HashSet<>(collected).size(), expectedUndirected); + } + + @Test + public void testFilteredSpliteratorSplitDropsSizedCharacteristic() { + int undirectedCount = GraphStoreConfiguration.EDGESTORE_BLOCK_SIZE + 256; + EdgeImpl[] edges = GraphGenerator.generateEdgeList(undirectedCount, 0, false, true, false); + EdgeStore edgeStore = new EdgeStore(); + edgeStore.addAll(Arrays.asList(edges)); + + Spliterator root = edgeStore.spliteratorUndirected(); + Assert.assertTrue((root.characteristics() & Spliterator.SIZED) != 0); + + Spliterator left = root.trySplit(); + Assert.assertNotNull(left); + Assert.assertTrue((root.characteristics() & Spliterator.SIZED) == 0); + Assert.assertTrue((left.characteristics() & Spliterator.SIZED) == 0); + } + + @Test + public void testEdgeSpliteratorCoversAll() { + NodeStore nodeStore = GraphGenerator.generateNodeStore(2); + NodeImpl n1 = nodeStore.get(0); + NodeImpl n2 = nodeStore.get(1); + EdgeStore store = new EdgeStore(); + int n = GraphStoreConfiguration.EDGESTORE_BLOCK_SIZE * 2 + 321; + for (int i = 0; i < n; i++) { + store.add(new EdgeImpl("e" + i, n1, n2, 0, 1.0, true)); + } + Set seen = new HashSet<>(); + Spliterator sp = store.spliterator(); + sp.forEachRemaining(seen::add); + Assert.assertEquals(seen.size(), n); + for (Edge e : store) { + Assert.assertTrue(seen.contains(e)); + } + } + + @Test(expectedExceptions = ConcurrentModificationException.class) + public void testEdgeSpliteratorFailFastOnAdd() { + EdgeImpl[] edges = GraphGenerator.generateSmallMultiTypeEdgeList(); + EdgeStore edgeStore = new EdgeStore(null, null, null, null, null, new GraphVersion(null)); + edgeStore.addAll(Arrays.asList(edges)); + Spliterator sp = edgeStore.spliterator(); + Assert.assertTrue(edgeStore.remove(edges[0])); + sp.tryAdvance(x -> { + }); + } + + @Test + public void testEdgeParallelStreamCount() { + EdgeImpl[] edges = GraphGenerator.generateEdgeList(100000, 2, true, true, true); + EdgeStore edgeStore = new EdgeStore(); + edgeStore.addAll(Arrays.asList(edges)); + long count = edgeStore.parallelStream().count(); + Assert.assertEquals(count, edges.length); + } + + @Test + public void testEdgeStream() { + EdgeStore store = new EdgeStore(); + EdgeImpl[] edges = GraphGenerator.generateLargeEdgeList(); + store.addAll(Arrays.asList(edges)); + + Set set = Collections.synchronizedSet(new HashSet<>()); + store.parallelStream().forEachOrdered(set::add); + Assert.assertEquals(set, store.toSet()); + } + @Test public void testUndirectedIteratorRemove() { EdgeStore edgeStore = new EdgeStore(); @@ -1753,7 +2083,7 @@ public void testInOutUndirectedIteratorRemove() { edgeStore.addAll(Arrays.asList(edges)); for (NodeImpl n : getNodes(edges)) { - Iterator itr = edgeStore.edgeUndirectedIterator(n); + Iterator itr = edgeStore.edgeUndirectedIterator(n, true); for (; itr.hasNext();) { itr.next(); itr.remove(); @@ -1770,7 +2100,7 @@ public void testInOutUndirectedIteratorRemoveDecorator() { edgeStore.addAll(Arrays.asList(edges)); for (NodeImpl n : getNodes(edges)) { - Iterator itr = edgeStore.edgeUndirectedIterator(n); + Iterator itr = edgeStore.edgeUndirectedIterator(n, true); for (; itr.hasNext();) { itr.next(); itr.remove(); @@ -1787,7 +2117,7 @@ public void testInOutUndirectedIterator() { Object2ObjectMap> neighbours = getNeighboursMap(edges, 0, true); for (NodeImpl n : getNodes(edges)) { - Iterator itr = edgeStore.edgeUndirectedIterator(n); + Iterator itr = edgeStore.edgeUndirectedIterator(n, true); Set incidentEdges = neighbours.get(n); @@ -1850,6 +2180,80 @@ public void testSelfLoopIterator() { Assert.assertEquals(count, selfLoops); } + @Test + public void testSetSameType() { + EdgeStore edgeStore = new EdgeStore(); + EdgeImpl edge = GraphGenerator.generateSingleEdge(4); + edgeStore.add(edge); + Assert.assertFalse(edgeStore.setEdgeType(edge, 4)); + } + + @Test + public void testSetTypeBeforeAdd() { + EdgeStore edgeStore = new EdgeStore(); + EdgeImpl edge = GraphGenerator.generateSingleEdge(4); + Assert.assertEquals(4, edge.getType()); + edgeStore.setEdgeType(edge, 1); + Assert.assertEquals(1, edge.getType()); + } + + @Test + public void testSetType() { + EdgeStore edgeStore = new EdgeStore(new EdgeTypeStore(), null, null, null, null, null); + EdgeImpl edge = GraphGenerator.generateSingleEdge(4); + edgeStore.add(edge); + edgeStore.setEdgeType(edge, 1); + Assert.assertEquals(1, edge.getType()); + Assert.assertSame(edge, edgeStore.get(edge.source, edge.target, 1, false)); + Assert.assertTrue(edgeStore.contains(edge.source, edge.target, 1)); + Assert.assertFalse(edgeStore.contains(edge.source, edge.target, 4)); + Assert.assertEquals(0, edgeStore.size(4)); + Assert.assertEquals(1, edgeStore.size(1)); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testAddWithoutAutoRegistration() { + ConfigurationImpl config = new ConfigurationImpl( + Configuration.builder().enableAutoEdgeTypeRegistration(false).build()); + EdgeStore edgeStore = new EdgeStore(new EdgeTypeStore(config), null, config, null, null, null); + EdgeImpl edge = GraphGenerator.generateSingleEdge(4); + edgeStore.add(edge); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testSetTypeWithoutAutoRegistration() { + ConfigurationImpl config = new ConfigurationImpl( + Configuration.builder().enableAutoEdgeTypeRegistration(false).build()); + EdgeStore edgeStore = new EdgeStore(new EdgeTypeStore(config), null, config, null, null, null); + EdgeImpl edge = GraphGenerator.generateSingleEdge(); + edgeStore.add(edge); + edgeStore.setEdgeType(edge, 1); + } + + @Test + public void testSetTypeWithMutualEdge() { + EdgeStore edgeStore = new EdgeStore(new EdgeTypeStore(), null, null, null, null, null); + EdgeImpl[] edges = GraphGenerator.generateMutualEdges(4); + edgeStore.addAll(Arrays.asList(edges)); + Assert.assertTrue(edges[0].isMutual()); + Assert.assertTrue(edges[1].isMutual()); + edgeStore.setEdgeType(edges[0], 1); + Assert.assertFalse(edges[0].isMutual()); + Assert.assertFalse(edges[1].isMutual()); + } + + @Test + public void testReturnFalseWithoutParallelEdges() { + ConfigurationImpl configuration = new ConfigurationImpl( + Configuration.builder().enableParallelEdgesSameType(false).build()); + EdgeStore edgeStore = new EdgeStore(new EdgeTypeStore(), null, configuration, null, null, null); + EdgeImpl edge = GraphGenerator.generateSingleEdge(1); + EdgeImpl edge2 = new EdgeImpl('0', edge.graphStore, edge.source, edge.target, 2, 1.0, true); + Assert.assertTrue(edgeStore.add(edge)); + Assert.assertTrue(edgeStore.add(edge2)); + Assert.assertFalse(edgeStore.setEdgeType(edge, 2)); + } + /* * UTILITY METHODS */ @@ -1958,10 +2362,10 @@ private NodeImpl[] getNodes(EdgeImpl[] edges) { return nodes.toArray(new NodeImpl[0]); } - public List iteratorToArray(Iterator edgeIterator) { - List list = new ArrayList<>(); + public List iteratorToArray(Iterator edgeIterator) { + List list = new ArrayList<>(); for (; edgeIterator.hasNext();) { - EdgeImpl e = edgeIterator.next(); + Edge e = edgeIterator.next(); list.add(e); } return list; diff --git a/src/test/java/org/gephi/graph/impl/EdgeTypeNoIndexTest.java b/src/test/java/org/gephi/graph/impl/EdgeTypeNoIndexTest.java new file mode 100644 index 00000000..782f4073 --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/EdgeTypeNoIndexTest.java @@ -0,0 +1,87 @@ +package org.gephi.graph.impl; + +import java.util.Collections; +import java.util.Iterator; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Graph; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class EdgeTypeNoIndexTest { + + @Test + public void testEmpty() { + GraphStore store = GraphGenerator.generateEmptyGraphStore(); + EdgeTypeNoIndexImpl index = new EdgeTypeNoIndexImpl(store); + Assert.assertEquals(index.countElements(), 0); + Assert.assertEquals(index.countValues(), 0); + Assert.assertEquals(index.count(null), 0); + Assert.assertFalse(index.get(null).iterator().hasNext()); + Assert.assertFalse(index.isSortable()); + Assert.assertSame(index.getColumn(), store.getModel().defaultColumns().edgeType()); + Assert.assertTrue(index.values().isEmpty()); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testMinValueException() { + GraphStore store = new GraphStore(); + EdgeTypeNoIndexImpl index = new EdgeTypeNoIndexImpl(store); + Assert.assertNull(index.getMinValue()); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testMaxValueException() { + GraphStore store = new GraphStore(); + EdgeTypeNoIndexImpl index = new EdgeTypeNoIndexImpl(store); + Assert.assertNull(index.getMaxValue()); + } + + @Test + public void testOneEdge() { + GraphStore store = GraphGenerator.generateTinyGraphStore(); + EdgeTypeNoIndexImpl index = new EdgeTypeNoIndexImpl(store); + Assert.assertEquals(index.countElements(), 1); + Assert.assertEquals(index.countValues(), 1); + Assert.assertEquals(index.count(null), 1); + } + + @Test + public void testSmallGraph() { + Graph graph = GraphGenerator.generateSmallMultiTypeGraphStore(); + + EdgeTypeNoIndexImpl index = new EdgeTypeNoIndexImpl(graph); + Assert.assertEquals(index.countElements(), graph.getEdgeCount()); + Assert.assertEquals(index.countValues(), 3); + Assert.assertEquals(index.count(null), graph.getEdgeCount(0)); + Assert.assertEquals(index.count("1"), graph.getEdgeCount(1)); + } + + @Test + public void testValues() { + Graph graph = GraphGenerator.generateTinyGraphStore(); + + EdgeTypeNoIndexImpl index = new EdgeTypeNoIndexImpl(graph); + Assert.assertEquals(index.values(), Collections.singletonList(null)); + } + + @Test + public void testGetIterator() { + Graph graph = GraphGenerator.generateTinyGraphStore(); + + EdgeTypeNoIndexImpl index = new EdgeTypeNoIndexImpl(graph); + Iterator itr = index.get(null).iterator(); + Assert.assertTrue(itr.hasNext()); + Assert.assertEquals(itr.next(), graph.getEdge("0")); + Assert.assertFalse(itr.hasNext()); + } + + @Test + public void testVersion() { + Graph graph = GraphGenerator.generateTinyGraphStore(); + + EdgeTypeNoIndexImpl index = new EdgeTypeNoIndexImpl(graph); + int version = index.getVersion(); + graph.removeEdge(graph.getEdge("0")); + Assert.assertNotEquals(index.getVersion(), version); + } +} diff --git a/store/src/test/java/org/gephi/graph/impl/EdgeTypeStoreTest.java b/src/test/java/org/gephi/graph/impl/EdgeTypeStoreTest.java similarity index 97% rename from store/src/test/java/org/gephi/graph/impl/EdgeTypeStoreTest.java rename to src/test/java/org/gephi/graph/impl/EdgeTypeStoreTest.java index 17e7a2c7..b531e34a 100644 --- a/store/src/test/java/org/gephi/graph/impl/EdgeTypeStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/EdgeTypeStoreTest.java @@ -15,6 +15,7 @@ */ package org.gephi.graph.impl; +import org.gephi.graph.api.Configuration; import org.testng.Assert; import org.testng.annotations.Test; @@ -25,6 +26,8 @@ public void testDefaultSize() { EdgeTypeStore edgeTypeStore = new EdgeTypeStore(); Assert.assertEquals(edgeTypeStore.size(), 1); + Assert.assertTrue(edgeTypeStore.contains(0)); + Assert.assertTrue(edgeTypeStore.contains(null)); } @Test @@ -281,8 +284,8 @@ public void testAddDirectTypeNoGarbage() { @Test public void testAddDifferentType() { - EdgeTypeStore edgeTypeStore = new EdgeTypeStore(); - edgeTypeStore.configuration.setEdgeLabelType(Integer.class); + EdgeTypeStore edgeTypeStore = new EdgeTypeStore( + new ConfigurationImpl(Configuration.builder().edgeLabelType(Integer.class).build())); int id = edgeTypeStore.addType(42); Assert.assertEquals(id, 1); Assert.assertEquals(edgeTypeStore.getLabel(1), 42); diff --git a/store/src/test/java/org/gephi/graph/impl/ElementImplTest.java b/src/test/java/org/gephi/graph/impl/ElementImplTest.java similarity index 75% rename from store/src/test/java/org/gephi/graph/impl/ElementImplTest.java rename to src/test/java/org/gephi/graph/impl/ElementImplTest.java index 95345d3b..382c5eef 100644 --- a/store/src/test/java/org/gephi/graph/impl/ElementImplTest.java +++ b/src/test/java/org/gephi/graph/impl/ElementImplTest.java @@ -15,6 +15,7 @@ */ package org.gephi.graph.impl; +import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -29,6 +30,7 @@ import org.gephi.graph.api.Origin; import org.gephi.graph.api.Estimator; import org.gephi.graph.api.Interval; +import org.gephi.graph.api.types.TimeSet; import org.gephi.graph.api.types.TimestampBooleanMap; import org.gephi.graph.api.types.TimestampByteMap; import org.gephi.graph.api.types.TimestampCharMap; @@ -84,8 +86,8 @@ public void testSetAttributeColumn() { NodeImpl node = new NodeImpl("0", store); node.setAttribute(column, 1); - Assert.assertEquals(node.attributes.length, 1 + getElementPropertiesLength()); - Assert.assertEquals(node.attributes[getFirstNonPropertyIndex()], 1); + Assert.assertEquals(node.getAttributes().length, 1 + getElementPropertiesLength()); + Assert.assertEquals(node.getAttributes()[getFirstNonPropertyIndex()], 1); Assert.assertEquals(node.getAttribute(column), 1); } @@ -97,17 +99,29 @@ public void testSetAttributeString() { NodeImpl node = new NodeImpl("0", store); node.setAttribute("age", 1); - Assert.assertEquals(node.attributes.length, 1 + getElementPropertiesLength()); - Assert.assertEquals(node.attributes[getFirstNonPropertyIndex()], 1); + Assert.assertEquals(node.getAttributes().length, 1 + getElementPropertiesLength()); + Assert.assertEquals(node.getAttributes()[getFirstNonPropertyIndex()], 1); Assert.assertEquals(node.getAttribute(column), 1); } + @Test + public void testSetAttributeInstant() { + GraphStore store = new GraphStore(); + Column column = generateBasicInstantColumn(store); + + Instant instant = Instant.parse("2014-01-01T00:00:00Z"); + NodeImpl node = new NodeImpl("0", store); + node.setAttribute("date", instant); + Assert.assertEquals(node.getAttribute(column), instant); + } + @Test public void testSetAttributeStandardizedType() { GraphStore store = new GraphStore(); - store.nodeTable.store - .addColumn(new ColumnImpl("arr1", Integer[].class, "Array", null, Origin.DATA, true, false)); - store.nodeTable.store.addColumn(new ColumnImpl("arr2", int[].class, "Array", null, Origin.DATA, true, false)); + store.nodeTable.store.addColumn(new ColumnImpl(store.nodeTable, "arr1", Integer[].class, "Array", null, + Origin.DATA, true, false)); + store.nodeTable.store.addColumn(new ColumnImpl(store.nodeTable, "arr2", int[].class, "Array", null, Origin.DATA, + true, false)); Column column1 = store.nodeTable.store.getColumn("arr1"); Column column2 = store.nodeTable.store.getColumn("arr2"); @@ -194,8 +208,25 @@ public void testSetAttributeTimestamp() { NodeImpl node = new NodeImpl("0", store); node.setAttribute(column, ti); - Assert.assertEquals(node.attributes.length, 1 + getElementPropertiesLength()); - Assert.assertEquals(node.attributes[getFirstNonPropertyIndex()], ti); + Assert.assertEquals(node.getAttributes().length, 1 + getElementPropertiesLength()); + Assert.assertEquals(node.getAttributes()[getFirstNonPropertyIndex()], ti); + Assert.assertEquals(node.getAttribute(column), ti); + } + + @Test + public void testSetAttributeInterval() { + GraphStore store = getIntervalGraphStore(); + Column column = generateIntervalColumn(store); + + IntervalIntegerMap ti = new IntervalIntegerMap(); + ti.put(new Interval(1.0, 2.0), 42); + ti.put(new Interval(2.0, 3.0), 10); + + NodeImpl node = new NodeImpl("0", store); + node.setAttribute(column, ti); + + Assert.assertEquals(node.getAttributes().length, 1 + getElementPropertiesLength()); + Assert.assertEquals(node.getAttributes()[getFirstNonPropertyIndex()], ti); Assert.assertEquals(node.getAttribute(column), ti); } @@ -214,6 +245,22 @@ public void testSetAttributeTimeset() { Assert.assertEquals(node.getAttribute(column), ti); } + @Test + public void testSetAttributeTimestampSet() { + GraphStore store = new GraphStore(); + Column column = generateTimesetColumn(store); + + TimestampSet ti = new TimestampSet(); + ti.add(1.0); + ti.add(2.0); + + NodeImpl node = new NodeImpl("0", store); + node.setAttribute(column, ti); + + Assert.assertEquals(node.getAttribute(column), ti); + Assert.assertEquals(node.getAttribute(column, store.mainGraphView), ti); + } + @Test public void testReplaceAttribute() { GraphStore store = new GraphStore(); @@ -261,8 +308,8 @@ public void testSetAttributeNull() { NodeImpl node = new NodeImpl("0", store); node.setAttribute(column, null); - Assert.assertEquals(node.attributes.length, 1 + getElementPropertiesLength()); - Assert.assertNull(node.attributes[getFirstNonPropertyIndex()]); + Assert.assertEquals(node.getAttributes().length, 1 + getElementPropertiesLength()); + Assert.assertNull(node.getAttributes()[getFirstNonPropertyIndex()]); Assert.assertNull(node.getAttribute(column)); } @@ -275,8 +322,8 @@ public void testSetAttributeTimestampColumn() { node.setAttribute(column, 1, 2.0); node.setAttribute(column, 2, 1.0); - Assert.assertEquals(node.attributes.length, 1 + getElementPropertiesLength()); - Assert.assertEquals(node.attributes[getFirstNonPropertyIndex()].getClass(), TimestampIntegerMap.class); + Assert.assertEquals(node.getAttributes().length, 1 + getElementPropertiesLength()); + Assert.assertEquals(node.getAttributes()[getFirstNonPropertyIndex()].getClass(), TimestampIntegerMap.class); Assert.assertEquals(node.getAttribute(column, 2.0), 1); Assert.assertEquals(node.getAttribute(column, 1.0), 2); } @@ -290,8 +337,8 @@ public void testSetAttributeIntervalColumn() { node.setAttribute(column, 1, new Interval(3.0, 4.0)); node.setAttribute(column, 2, new Interval(1.0, 2.0)); - Assert.assertEquals(node.attributes.length, 1 + getElementPropertiesLength()); - Assert.assertEquals(node.attributes[getFirstNonPropertyIndex()].getClass(), IntervalIntegerMap.class); + Assert.assertEquals(node.getAttributes().length, 1 + getElementPropertiesLength()); + Assert.assertEquals(node.getAttributes()[getFirstNonPropertyIndex()].getClass(), IntervalIntegerMap.class); Assert.assertEquals(node.getAttribute(column, new Interval(3.0, 4.0)), 1); Assert.assertEquals(node.getAttribute(column, new Interval(1.0, 2.0)), 2); } @@ -418,6 +465,21 @@ public void testGetAttributeKey() { Assert.assertEquals(res, 1); } + @Test + public void testGetAttributeDefaultColumns() { + GraphStore store = GraphGenerator.generateTinyGraphStore(); + Node node = store.getNode("1"); + Edge edge = store.getEdge("0"); + + Assert.assertEquals(node.getAttribute(store.getModel().defaultColumns().nodeId()), "1"); + Assert.assertNull(node.getAttribute(store.getModel().defaultColumns().nodeLabel())); + Assert.assertNull(node.getAttribute(store.getModel().defaultColumns().nodeTimeSet())); + + Assert.assertEquals(edge.getAttribute(store.getModel().defaultColumns().edgeId()), "0"); + Assert.assertNull(edge.getAttribute(store.getModel().defaultColumns().edgeLabel())); + Assert.assertNull(edge.getAttribute(store.getModel().defaultColumns().edgeTimeSet())); + } + @Test(expectedExceptions = IllegalArgumentException.class) public void testGetAttributeKeyUnknown() { GraphStore store = new GraphStore(); @@ -539,7 +601,8 @@ public void testGetAttributeNonTimestamp() { public void testGetDefaultValue() { GraphStore store = new GraphStore(); Integer defaultValue = 25; - Column column = new ColumnImpl("age", Integer.class, "Age", defaultValue, Origin.DATA, true, false); + Column column = new ColumnImpl(store.nodeTable, "age", Integer.class, "Age", defaultValue, Origin.DATA, true, + false); store.nodeTable.store.addColumn(column); NodeImpl node = new NodeImpl("0", store); @@ -548,11 +611,15 @@ public void testGetDefaultValue() { node.setAttribute(column, null); res = node.getAttribute(column.getId()); - Assert.assertEquals(res, defaultValue); + Assert.assertNull(res); node.setAttribute(column, 1); res = node.getAttribute(column.getId()); Assert.assertEquals(res, 1); + + node.removeAttribute(column); + res = node.getAttribute(column.getId()); + Assert.assertEquals(res, defaultValue); } @Test @@ -1003,6 +1070,15 @@ public void testGetDynamicAttributesStaticColumn() { node.getAttributes(column); } + @Test(expectedExceptions = IllegalArgumentException.class) + public void testGetDynamicAttributesTimeset() { + GraphStore store = new GraphStore(); + Column column = store.defaultColumns.nodeTimeSet(); + + NodeImpl node = new NodeImpl("0", store); + node.getAttributes(column); + } + @Test public void testGetAttributeInView() { GraphStore store = new GraphStore(); @@ -1041,29 +1117,77 @@ public void testGetTimestampAttributeInView() { } @Test - public void testCheckType() { + public void testCheckDynamicType() { GraphStore store = new GraphStore(); NodeImpl node = new NodeImpl("0", store); - node.checkType(new ColumnImpl("0", TimestampIntegerMap.class, null, null, Origin.DATA, false, false), 1); - node.checkType(new ColumnImpl("0", TimestampDoubleMap.class, null, null, Origin.DATA, false, false), 1.0); - node.checkType(new ColumnImpl("0", TimestampFloatMap.class, null, null, Origin.DATA, false, false), 1f); - node.checkType(new ColumnImpl("0", TimestampByteMap.class, null, null, Origin.DATA, false, false), (byte) 1); - node.checkType(new ColumnImpl("0", TimestampShortMap.class, null, null, Origin.DATA, false, false), (short) 1); - node.checkType(new ColumnImpl("0", TimestampLongMap.class, null, null, Origin.DATA, false, false), 1l); - node.checkType(new ColumnImpl("0", TimestampCharMap.class, null, null, Origin.DATA, false, false), 'a'); - node.checkType(new ColumnImpl("0", TimestampBooleanMap.class, null, null, Origin.DATA, false, false), true); - node.checkType(new ColumnImpl("0", TimestampStringMap.class, null, null, Origin.DATA, false, false), "foo"); - node.checkType(new ColumnImpl("0", IntervalIntegerMap.class, null, null, Origin.DATA, false, false), 1); - node.checkType(new ColumnImpl("0", IntervalDoubleMap.class, null, null, Origin.DATA, false, false), 1.0); - node.checkType(new ColumnImpl("0", IntervalFloatMap.class, null, null, Origin.DATA, false, false), 1f); - node.checkType(new ColumnImpl("0", IntervalByteMap.class, null, null, Origin.DATA, false, false), (byte) 1); - node.checkType(new ColumnImpl("0", IntervalShortMap.class, null, null, Origin.DATA, false, false), (short) 1); - node.checkType(new ColumnImpl("0", IntervalLongMap.class, null, null, Origin.DATA, false, false), 1l); - node.checkType(new ColumnImpl("0", IntervalCharMap.class, null, null, Origin.DATA, false, false), 'a'); - node.checkType(new ColumnImpl("0", IntervalBooleanMap.class, null, null, Origin.DATA, false, false), true); - node.checkType(new ColumnImpl("0", IntervalStringMap.class, null, null, Origin.DATA, false, false), "foo"); + node.checkDynamicType(new ColumnImpl("0", TimestampIntegerMap.class, null, null, Origin.DATA, false, false), 1); + node.checkDynamicType(new ColumnImpl("0", TimestampDoubleMap.class, null, null, Origin.DATA, false, + false), 1.0); + node.checkDynamicType(new ColumnImpl("0", TimestampFloatMap.class, null, null, Origin.DATA, false, false), 1f); + node.checkDynamicType(new ColumnImpl("0", TimestampByteMap.class, null, null, Origin.DATA, false, + false), (byte) 1); + node.checkDynamicType(new ColumnImpl("0", TimestampShortMap.class, null, null, Origin.DATA, false, + false), (short) 1); + node.checkDynamicType(new ColumnImpl("0", TimestampLongMap.class, null, null, Origin.DATA, false, false), 1l); + node.checkDynamicType(new ColumnImpl("0", TimestampCharMap.class, null, null, Origin.DATA, false, false), 'a'); + node.checkDynamicType(new ColumnImpl("0", TimestampBooleanMap.class, null, null, Origin.DATA, false, + false), true); + node.checkDynamicType(new ColumnImpl("0", TimestampStringMap.class, null, null, Origin.DATA, false, + false), "foo"); + } + + @Test(expectedExceptions = RuntimeException.class) + public void testCheckDynamicTypeWithWrongIntervalConfiguration() { + GraphStore store = new GraphStore(); + NodeImpl node = new NodeImpl("0", store); + node.checkDynamicType(new ColumnImpl("0", IntervalIntegerMap.class, null, null, Origin.DATA, false, false), 1); + } + + @Test(expectedExceptions = RuntimeException.class) + public void testCheckDynamicTypeWithWrongTimestampConfiguration() { + GraphStore store = getIntervalGraphStore(); + + NodeImpl node = new NodeImpl("0", store); + node.checkDynamicType(new ColumnImpl("0", TimestampIntegerMap.class, null, null, Origin.DATA, false, false), 1); + } + + @Test + public void testDynamicCheckTypeInterval() { + GraphStore store = getIntervalGraphStore(); + + NodeImpl node = new NodeImpl("0", store); + node.checkDynamicType(new ColumnImpl("0", IntervalIntegerMap.class, null, null, Origin.DATA, false, false), 1); + node.checkDynamicType(new ColumnImpl("0", IntervalDoubleMap.class, null, null, Origin.DATA, false, false), 1.0); + node.checkDynamicType(new ColumnImpl("0", IntervalFloatMap.class, null, null, Origin.DATA, false, false), 1f); + node.checkDynamicType(new ColumnImpl("0", IntervalByteMap.class, null, null, Origin.DATA, false, + false), (byte) 1); + node.checkDynamicType(new ColumnImpl("0", IntervalShortMap.class, null, null, Origin.DATA, false, + false), (short) 1); + node.checkDynamicType(new ColumnImpl("0", IntervalLongMap.class, null, null, Origin.DATA, false, false), 1l); + node.checkDynamicType(new ColumnImpl("0", IntervalCharMap.class, null, null, Origin.DATA, false, false), 'a'); + node.checkDynamicType(new ColumnImpl("0", IntervalBooleanMap.class, null, null, Origin.DATA, false, + false), true); + node.checkDynamicType(new ColumnImpl("0", IntervalStringMap.class, null, null, Origin.DATA, false, + false), "foo"); + } + + @Test + public void checkType() { + GraphStore store = new GraphStore(); + + NodeImpl node = new NodeImpl("0", store); + node.checkType(new ColumnImpl("0", Integer.class, null, null, Origin.DATA, false, false), 1); + node.checkType(new ColumnImpl("0", Double.class, null, null, Origin.DATA, false, false), 1.0); + node.checkType(new ColumnImpl("0", Float.class, null, null, Origin.DATA, false, false), 1f); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void checkTypeException() { + GraphStore store = new GraphStore(); + NodeImpl node = new NodeImpl("0", store); + node.checkType(new ColumnImpl("0", Integer.class, null, null, Origin.DATA, false, false), "foo"); } @Test @@ -1074,55 +1198,112 @@ public void testGetTable() { } } + @Test + public void testRemoveColumn() { + GraphStore store = new GraphStore(); + Column column = generateBasicColumn(store); + + NodeImpl node = new NodeImpl("0", store); + node.setAttribute(column, 1); + store.addNode(node); + + int index = column.getIndex(); + column.getTable().removeColumn(column); + Assert.assertNull(node.getAttributes()[index]); + } + + @Test + public void testRemoveColumnDefaultValue() { + GraphStore store = new GraphStore(); + Column column = new ColumnImpl(store.nodeTable, "age", Integer.class, "Age", 25, Origin.DATA, true, false); + store.nodeTable.store.addColumn(column); + + NodeImpl node = new NodeImpl("0", store); + node.setAttribute(column, 1); + store.addNode(node); + + int index = column.getIndex(); + column.getTable().removeColumn(column); + Assert.assertNull(node.getAttributes()[index]); + } + + @Test + public void testEnsureCapacity() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + Node n1 = graphStore.getNode("1"); + Assert.assertTrue(n1.getAttributes().length < 100); + for (int i = 0; i < 100; i++) { + Column col = new ColumnImpl(graphStore.nodeTable, "col" + i, Integer.class, "Age", null, Origin.DATA, true, + false); + graphStore.nodeTable.store.addColumn(col); + Assert.assertNull(n1.getAttribute(col)); + n1.setAttribute(col, i); + Assert.assertEquals(i, n1.getAttribute(col)); + } + } + // Utility private GraphStore getIntervalGraphStore() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); GraphStore store = graphModel.store; return store; } private Column generateBasicColumn(GraphStore graphStore) { - graphStore.nodeTable.store - .addColumn(new ColumnImpl("age", Integer.class, "Age", null, Origin.DATA, true, false)); + graphStore.nodeTable.store.addColumn(new ColumnImpl(graphStore.nodeTable, "age", Integer.class, "Age", null, + Origin.DATA, true, false)); return graphStore.nodeTable.store.getColumn("age"); } private Column generateBasicBooleanColumn(GraphStore graphStore) { - graphStore.nodeTable.store.addColumn(new ColumnImpl("visible", Boolean.class, "Visible", null, Origin.DATA, - true, false)); + graphStore.nodeTable.store.addColumn(new ColumnImpl(graphStore.nodeTable, "visible", Boolean.class, "Visible", + null, Origin.DATA, true, false)); return graphStore.nodeTable.store.getColumn("visible"); } + private Column generateBasicInstantColumn(GraphStore graphStore) { + graphStore.nodeTable.store.addColumn(new ColumnImpl(graphStore.nodeTable, "date", Instant.class, "Date", null, + Origin.DATA, true, false)); + return graphStore.nodeTable.store.getColumn("date"); + } + private Column generateBasicListColumn(GraphStore graphStore) { - graphStore.nodeTable.store - .addColumn(new ColumnImpl("list", List.class, "List", null, Origin.DATA, true, false)); + graphStore.nodeTable.store.addColumn(new ColumnImpl(graphStore.nodeTable, "list", List.class, "List", null, + Origin.DATA, true, false)); return graphStore.nodeTable.store.getColumn("list"); } private Column generateBasicSetColumn(GraphStore graphStore) { - graphStore.nodeTable.store.addColumn(new ColumnImpl("set", Set.class, "Set", null, Origin.DATA, true, false)); + graphStore.nodeTable.store.addColumn(new ColumnImpl(graphStore.nodeTable, "set", Set.class, "Set", null, + Origin.DATA, true, false)); return graphStore.nodeTable.store.getColumn("set"); } private Column generateBasicMapColumn(GraphStore graphStore) { - graphStore.nodeTable.store.addColumn(new ColumnImpl("map", Map.class, "Map", null, Origin.DATA, true, false)); + graphStore.nodeTable.store.addColumn(new ColumnImpl(graphStore.nodeTable, "map", Map.class, "Map", null, + Origin.DATA, true, false)); return graphStore.nodeTable.store.getColumn("map"); } private Column generateTimestampColumn(GraphStore graphStore) { - graphStore.nodeTable.store.addColumn(new ColumnImpl("age", TimestampIntegerMap.class, "Age", null, Origin.DATA, - false, false)); + graphStore.nodeTable.store.addColumn(new ColumnImpl(graphStore.nodeTable, "age", TimestampIntegerMap.class, + "Age", null, Origin.DATA, false, false)); return graphStore.nodeTable.store.getColumn("age"); } private Column generateIntervalColumn(GraphStore graphStore) { - graphStore.nodeTable.store.addColumn(new ColumnImpl("age", IntervalIntegerMap.class, "Age", null, Origin.DATA, - false, false)); + graphStore.nodeTable.store.addColumn(new ColumnImpl(graphStore.nodeTable, "age", IntervalIntegerMap.class, + "Age", null, Origin.DATA, false, false)); return graphStore.nodeTable.store.getColumn("age"); } + private Column generateTimesetColumn(GraphStore graphStore) { + graphStore.nodeTable.store.addColumn(new ColumnImpl(graphStore.nodeTable, "events", TimestampSet.class, + "Events", null, Origin.DATA, false, false)); + return graphStore.nodeTable.store.getColumn("events"); + } + // Properties size public int getElementPropertiesLength() { return 1 + (ENABLE_ELEMENT_LABEL ? 1 : 0) + (ENABLE_ELEMENT_TIME_SET ? 1 : 0); diff --git a/store/src/test/java/org/gephi/graph/impl/ElementPropertiesTest.java b/src/test/java/org/gephi/graph/impl/ElementPropertiesTest.java similarity index 96% rename from store/src/test/java/org/gephi/graph/impl/ElementPropertiesTest.java rename to src/test/java/org/gephi/graph/impl/ElementPropertiesTest.java index 0f3fd79a..b04cf8ea 100644 --- a/store/src/test/java/org/gephi/graph/impl/ElementPropertiesTest.java +++ b/src/test/java/org/gephi/graph/impl/ElementPropertiesTest.java @@ -26,6 +26,17 @@ */ public class ElementPropertiesTest { + @Test + public void testDefaultsAlpha() { + NodeImpl.NodePropertiesImpl p = new NodeImpl.NodePropertiesImpl(); + Assert.assertEquals(p.alpha(), 1f); + Assert.assertEquals(p.getTextProperties().getAlpha(), 1f); + + EdgeImpl.EdgePropertiesImpl ep = new EdgeImpl.EdgePropertiesImpl(); + Assert.assertEquals(ep.alpha(), 1f); + Assert.assertEquals(ep.getTextProperties().getAlpha(), 1f); + } + @Test public void testNodeProperties() { NodeImpl.NodePropertiesImpl p = new NodeImpl.NodePropertiesImpl(); diff --git a/store/src/test/java/org/gephi/graph/impl/EmptyIterableTest.java b/src/test/java/org/gephi/graph/impl/EmptyIterableTest.java similarity index 77% rename from store/src/test/java/org/gephi/graph/impl/EmptyIterableTest.java rename to src/test/java/org/gephi/graph/impl/EmptyIterableTest.java index 5650fa94..e33283f4 100644 --- a/store/src/test/java/org/gephi/graph/impl/EmptyIterableTest.java +++ b/src/test/java/org/gephi/graph/impl/EmptyIterableTest.java @@ -16,8 +16,10 @@ package org.gephi.graph.impl; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.stream.Collectors; import org.gephi.graph.api.Edge; import org.gephi.graph.api.EdgeIterable; import org.gephi.graph.api.Element; @@ -62,6 +64,15 @@ public void testEdgeIterableDoBreak() { EdgeIterable.EMPTY.doBreak(); } + @Test + public void testEdgeIterableSpliterator() { + EdgeIterable itr = EdgeIterable.EMPTY; + Assert.assertFalse(itr.spliterator().tryAdvance((e) -> { + })); + Assert.assertEquals(itr.spliterator().estimateSize(), 0); + Assert.assertEquals(itr.stream().collect(Collectors.toList()), Collections.EMPTY_LIST); + } + @Test public void testNodeIterableHasNext() { Iterator itr = NodeIterable.EMPTY.iterator(); @@ -95,6 +106,15 @@ public void testNodeIterableDoBreak() { NodeIterable.EMPTY.doBreak(); } + @Test + public void testNodeIterableSpliterator() { + NodeIterable itr = NodeIterable.EMPTY; + Assert.assertFalse(itr.spliterator().tryAdvance((e) -> { + })); + Assert.assertEquals(itr.spliterator().estimateSize(), 0); + Assert.assertEquals(itr.stream().collect(Collectors.toList()), Collections.EMPTY_LIST); + } + @Test public void testElementIterableHasNext() { Iterator itr = ElementIterable.EMPTY.iterator(); @@ -127,4 +147,13 @@ public void testElementIterableToCollection() { public void testElementIterableDoBreak() { ElementIterable.EMPTY.doBreak(); } + + @Test + public void testElementIterableSpliterator() { + ElementIterable itr = ElementIterable.EMPTY; + Assert.assertFalse(itr.spliterator().tryAdvance((e) -> { + })); + Assert.assertEquals(itr.spliterator().estimateSize(), 0); + Assert.assertEquals(itr.stream().collect(Collectors.toList()), Collections.EMPTY_LIST); + } } diff --git a/store/src/test/java/org/gephi/graph/impl/EstimatorTest.java b/src/test/java/org/gephi/graph/impl/EstimatorTest.java similarity index 100% rename from store/src/test/java/org/gephi/graph/impl/EstimatorTest.java rename to src/test/java/org/gephi/graph/impl/EstimatorTest.java diff --git a/store/src/test/java/org/gephi/graph/impl/GraphAttributesTest.java b/src/test/java/org/gephi/graph/impl/GraphAttributesTest.java similarity index 100% rename from store/src/test/java/org/gephi/graph/impl/GraphAttributesTest.java rename to src/test/java/org/gephi/graph/impl/GraphAttributesTest.java diff --git a/store/src/test/java/org/gephi/graph/impl/GraphBridgeTest.java b/src/test/java/org/gephi/graph/impl/GraphBridgeTest.java similarity index 73% rename from store/src/test/java/org/gephi/graph/impl/GraphBridgeTest.java rename to src/test/java/org/gephi/graph/impl/GraphBridgeTest.java index 8f5b08e5..3a838d7b 100644 --- a/store/src/test/java/org/gephi/graph/impl/GraphBridgeTest.java +++ b/src/test/java/org/gephi/graph/impl/GraphBridgeTest.java @@ -29,6 +29,7 @@ import org.gephi.graph.api.types.IntervalIntegerMap; import org.gephi.graph.api.types.TimestampDoubleMap; import org.gephi.graph.api.types.TimestampIntegerMap; +import org.gephi.graph.api.types.TimestampSet; import org.testng.Assert; import org.testng.annotations.Test; @@ -36,8 +37,16 @@ public class GraphBridgeTest { @Test(expectedExceptions = RuntimeException.class) public void testVerifyConfiguration() { - Configuration destConfig = new Configuration(); - destConfig.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration destConfig = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); + GraphModelImpl dest = new GraphModelImpl(destConfig); + + new GraphBridgeImpl(dest.store).copyNodes(GraphGenerator.generateTinyGraphStore().getNodes().toArray()); + } + + @Test(expectedExceptions = RuntimeException.class) + public void testVerifyConfigurationParallelEdges() { + Configuration destConfig = Configuration.builder() + .enableParallelEdgesSameType(!GraphStoreConfiguration.DEFAULT_ENABLE_PARALLEL_EDGES_SAME_TYPE).build(); GraphModelImpl dest = new GraphModelImpl(destConfig); new GraphBridgeImpl(dest.store).copyNodes(GraphGenerator.generateTinyGraphStore().getNodes().toArray()); @@ -143,6 +152,8 @@ public void testCopyNodeTextProperties() { n1.getTextProperties().setAlpha(0.5f); n1.getTextProperties().setSize(5f); n1.getTextProperties().setVisible(false); + n1.getTextProperties().setText("foo"); + n1.getTextProperties().setDimensions(2f, 3f); GraphStore dest = new GraphStore(); new GraphBridgeImpl(dest).copyNodes(source.getNodes().toArray()); @@ -159,6 +170,8 @@ public void testCopyEdgeTextProperties() { e0.getTextProperties().setAlpha(0.5f); e0.getTextProperties().setSize(5f); e0.getTextProperties().setVisible(false); + e0.getTextProperties().setText("foo"); + e0.getTextProperties().setDimensions(2f, 3f); GraphStore dest = new GraphStore(); new GraphBridgeImpl(dest).copyNodes(source.getNodes().toArray()); @@ -182,8 +195,7 @@ public void testCopyEdgeWeightStatic() { @Test public void testCopyEdgeWeightTimestamp() { - Configuration config = new Configuration(); - config.setEdgeWeightType(TimestampDoubleMap.class); + Configuration config = Configuration.builder().edgeWeightType(TimestampDoubleMap.class).build(); GraphStore source = GraphGenerator.generateTinyGraphStore(config); EdgeImpl e0 = source.getEdge("0"); e0.setWeight(42.0, 1.0); @@ -200,15 +212,13 @@ public void testCopyEdgeWeightTimestamp() { @Test public void testCopyEdgeWeightInterval() { - Configuration config = new Configuration(); - config.setEdgeWeightType(IntervalDoubleMap.class); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL) + .edgeWeightType(IntervalDoubleMap.class).build(); GraphStore source = GraphGenerator.generateTinyGraphStore(config); EdgeImpl e0 = source.getEdge("0"); e0.setWeight(42.0, new Interval(1.0, 2.0)); e0.setWeight(5.0, new Interval(3.0, 4.0)); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); GraphModelImpl gm = new GraphModelImpl(config); GraphStore dest = gm.store; new GraphBridgeImpl(dest).copyNodes(source.getNodes().toArray()); @@ -242,7 +252,9 @@ public void testCopyPartial() { } } for (Integer typeId : typeIds) { - source.edgeTypeStore.addType(String.valueOf(typeId), typeId); + if (typeId != EdgeTypeStore.NULL_LABEL) { + source.edgeTypeStore.addType(String.valueOf(typeId), typeId); + } } new GraphBridgeImpl(dest).copyNodes(source.getNodes().toArray()); @@ -298,8 +310,7 @@ public void testCopyNodeAttributesInterval() { n1.setAttribute(c2, 10, new Interval(1.0, 2.0)); n1.setAttribute(c2, 20, new Interval(3.0, 4.0)); - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl model = new GraphModelImpl(config); GraphStore dest = model.store; new GraphBridgeImpl(dest).copyNodes(source.getNodes().toArray()); @@ -330,4 +341,88 @@ public void testCopyEdgeAttributes() { Assert.assertEquals(e0Copy.getAttribute(c2Copy, 1.0), 10); Assert.assertEquals(e0Copy.getAttribute(c2Copy, 2.0), 20); } + + @Test + public void testCopyTimestampSet() { + GraphStore source = GraphGenerator.generateTinyGraphStore(); + Node n1 = source.getNode("1"); + n1.addTimestamp(42.0); + + GraphModelImpl model = new GraphModelImpl(); + GraphStore dest = model.store; + new GraphBridgeImpl(dest).copyNodes(source.getNodes().toArray()); + + Node n1Copy = dest.getNode("1"); + Assert.assertTrue(n1Copy.hasTimestamp(42.0)); + n1.addTimestamp(43.0); + Assert.assertFalse(n1Copy.hasTimestamp(43.0)); + } + + @Test + public void testCopyIntervalSet() { + GraphStore source = GraphGenerator.generateTinyGraphStore(TimeRepresentation.INTERVAL); + Node n1 = source.getNode("1"); + n1.addInterval(new Interval(1.0, 2.0)); + + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); + GraphModelImpl model = new GraphModelImpl(config); + GraphStore dest = model.store; + new GraphBridgeImpl(dest).copyNodes(source.getNodes().toArray()); + + Node n1Copy = dest.getNode("1"); + Assert.assertTrue(n1Copy.hasInterval(new Interval(1.0, 2.0))); + n1.addInterval(new Interval(3.0, 4.0)); + Assert.assertFalse(n1Copy.hasInterval(new Interval(3.0, 4.0))); + } + + @Test + public void testCopyArray() { + GraphStore source = GraphGenerator.generateTinyGraphStore(); + Column c1 = source.nodeTable.addColumn("foo", int[].class); + Node n1 = source.getNode("1"); + int[] a1 = new int[] { 1, 2, 3 }; + n1.setAttribute(c1, a1); + + GraphModelImpl model = new GraphModelImpl(); + GraphStore dest = model.store; + new GraphBridgeImpl(dest).copyNodes(source.getNodes().toArray()); + + Node n1Copy = dest.getNode("1"); + Assert.assertEquals(n1Copy.getAttribute(c1.getId()), a1); + a1[0] = 4; + Assert.assertNotEquals(n1Copy.getAttribute(c1.getId()), a1); + } + + @Test + public void testCopyOtherTimesetColumn() { + GraphStore source = GraphGenerator.generateTinyGraphStore(); + Column c1 = source.nodeTable.addColumn("foo", TimestampSet.class); + Node n1 = source.getNode("1"); + TimestampSet set = new TimestampSet(); + set.add(42.0); + n1.setAttribute(c1, set); + + GraphModelImpl model = new GraphModelImpl(); + GraphStore dest = model.store; + new GraphBridgeImpl(dest).copyNodes(source.getNodes().toArray()); + + Node n1Copy = dest.getNode("1"); + Assert.assertEquals(n1Copy.getAttribute(c1.getId()), set); + set.add(43.0); + Assert.assertNotEquals(n1Copy.getAttribute(c1.getId()), set); + } + + @Test + public void testCopyOnNonEmpty() { + GraphStore dest = GraphGenerator.generateTinyGraphStore(); + + GraphStore source = GraphGenerator.generateEmptyGraphStore(); + Node n1 = source.getModel().factory().newNode("foo"); + source.addNode(n1); + + new GraphBridgeImpl(dest).copyNodes(source.getNodes().toArray()); + Assert.assertNotNull(dest.getNode("foo")); + Assert.assertEquals(dest.getNodeCount(), 3); + Assert.assertEquals(dest.getEdgeCount(), 1); + } } diff --git a/store/src/test/java/org/gephi/graph/impl/GraphFactoryTest.java b/src/test/java/org/gephi/graph/impl/GraphFactoryTest.java similarity index 92% rename from store/src/test/java/org/gephi/graph/impl/GraphFactoryTest.java rename to src/test/java/org/gephi/graph/impl/GraphFactoryTest.java index d82a992c..ff5bb469 100644 --- a/store/src/test/java/org/gephi/graph/impl/GraphFactoryTest.java +++ b/src/test/java/org/gephi/graph/impl/GraphFactoryTest.java @@ -132,8 +132,7 @@ public void testNewEdgeWithId() { @Test public void testIntegerNodeId() { - Configuration config = new Configuration(); - config.setNodeIdType(Integer.class); + Configuration config = Configuration.builder().nodeIdType(Integer.class).build(); GraphModelImpl graphModel = new GraphModelImpl(config); GraphFactoryImpl graphFactory = new GraphFactoryImpl(graphModel.store); Node node = graphFactory.newNode(); @@ -142,8 +141,7 @@ public void testIntegerNodeId() { @Test public void testIntegerEdgeId() { - Configuration config = new Configuration(); - config.setEdgeIdType(Integer.class); + Configuration config = Configuration.builder().edgeIdType(Integer.class).build(); GraphModelImpl graphModel = new GraphModelImpl(config); GraphFactoryImpl graphFactory = new GraphFactoryImpl(graphModel.store); @@ -156,8 +154,7 @@ public void testIntegerEdgeId() { @Test(expectedExceptions = UnsupportedOperationException.class) public void testUnsupportedNodeId() { - Configuration config = new Configuration(); - config.setNodeIdType(Float.class); + Configuration config = Configuration.builder().nodeIdType(Float.class).build(); GraphModelImpl graphModel = new GraphModelImpl(config); GraphFactoryImpl graphFactory = new GraphFactoryImpl(graphModel.store); graphFactory.newNode(); @@ -165,8 +162,7 @@ public void testUnsupportedNodeId() { @Test(expectedExceptions = UnsupportedOperationException.class) public void testUnsupportedEdgeId() { - Configuration config = new Configuration(); - config.setEdgeIdType(Float.class); + Configuration config = Configuration.builder().edgeIdType(Float.class).build(); GraphModelImpl graphModel = new GraphModelImpl(config); GraphFactoryImpl graphFactory = new GraphFactoryImpl(graphModel.store); @@ -178,8 +174,7 @@ public void testUnsupportedEdgeId() { @Test public void testAutoIncrementNodeInt() { - Configuration config = new Configuration(); - config.setNodeIdType(Integer.class); + Configuration config = Configuration.builder().nodeIdType(Integer.class).build(); GraphModelImpl graphModel = new GraphModelImpl(config); GraphFactoryImpl graphFactory = new GraphFactoryImpl(graphModel.store); graphFactory.newNode(10); @@ -192,8 +187,7 @@ public void testAutoIncrementNodeInt() { @Test public void testAutoIncrementEdgeInt() { - Configuration config = new Configuration(); - config.setEdgeIdType(Integer.class); + Configuration config = Configuration.builder().edgeIdType(Integer.class).build(); GraphModelImpl graphModel = new GraphModelImpl(config); GraphFactoryImpl graphFactory = new GraphFactoryImpl(graphModel.store); Node n1 = graphFactory.newNode(); @@ -210,7 +204,7 @@ public void testAutoIncrementEdgeInt() { @Test public void testAutoIncrementNodeString() { - GraphModelImpl graphModel = new GraphModelImpl(new Configuration()); + GraphModelImpl graphModel = new GraphModelImpl(); GraphFactoryImpl graphFactory = new GraphFactoryImpl(graphModel.store); graphFactory.newNode("10"); Assert.assertEquals(graphFactory.newNode().getId(), "11"); @@ -224,7 +218,7 @@ public void testAutoIncrementNodeString() { @Test public void testAutoIncrementNotInteger() { - GraphModelImpl graphModel = new GraphModelImpl(new Configuration()); + GraphModelImpl graphModel = new GraphModelImpl(); GraphFactoryImpl graphFactory = new GraphFactoryImpl(graphModel.store); graphFactory.newNode("3543543523242"); Assert.assertEquals(graphFactory.newNode().getId(), "0"); @@ -232,7 +226,7 @@ public void testAutoIncrementNotInteger() { @Test public void testAutoIncrementEdgeString() { - GraphModelImpl graphModel = new GraphModelImpl(new Configuration()); + GraphModelImpl graphModel = new GraphModelImpl(); GraphFactoryImpl graphFactory = new GraphFactoryImpl(graphModel.store); Node n1 = graphFactory.newNode(); Node n2 = graphFactory.newNode(); diff --git a/store/src/test/java/org/gephi/graph/impl/GraphGenerator.java b/src/test/java/org/gephi/graph/impl/GraphGenerator.java similarity index 78% rename from store/src/test/java/org/gephi/graph/impl/GraphGenerator.java rename to src/test/java/org/gephi/graph/impl/GraphGenerator.java index 773ce21c..07f4947c 100644 --- a/store/src/test/java/org/gephi/graph/impl/GraphGenerator.java +++ b/src/test/java/org/gephi/graph/impl/GraphGenerator.java @@ -29,6 +29,7 @@ import org.gephi.graph.api.Configuration; import org.gephi.graph.api.Edge; import org.gephi.graph.api.Node; +import org.gephi.graph.api.Rect2D; import org.gephi.graph.api.TimeRepresentation; import org.gephi.graph.impl.BasicGraphStore.BasicEdgeStore; @@ -86,6 +87,16 @@ public static EdgeImpl generateSingleEdge(int type) { return generateEdgeList(1, type, true, false, false)[0]; } + public static EdgeImpl[] generateMutualEdges(int type) { + EdgeImpl e1 = generateSingleEdge(type); + EdgeImpl e2 = generateOppositeEdge(e1); + return new EdgeImpl[] { e1, e2 }; + } + + public static EdgeImpl generateOppositeEdge(EdgeImpl edge) { + return new EdgeImpl("-" + edge.getId().toString(), edge.target, edge.source, edge.type, 1.0, true); + } + public static EdgeImpl generateSelfLoop(int type, boolean directed) { NodeStore nodeStore = generateNodeStore(2); EdgeImpl edge = new EdgeImpl('0', nodeStore.get(0), nodeStore.get(0), type, 1.0, directed); @@ -141,15 +152,16 @@ public static EdgeImpl[] generateEdgeList(NodeStore nodeStore, int edgeCount, in graphStore = nodeStore.viewStore.graphStore; } - int c = 0; + int c = type * edgeCount; while (idSet.size() < edgeCount) { int sourceId = r.nextInt(nodeCount); int targetId = r.nextInt(nodeCount); NodeImpl source = nodeStore.get(sourceId); NodeImpl target = nodeStore.get(targetId); EdgeImpl edge = new EdgeImpl(String.valueOf(c), graphStore, source, target, type, 1.0, directed); - if (!leafs.contains(sourceId) && !leafs.contains(targetId) && (allowSelfLoops || (!allowSelfLoops && source != target)) && (allowParallel || !idSet - .contains(edge.getLongId()))) { + if (!leafs.contains(sourceId) && !leafs + .contains(targetId) && (allowSelfLoops || (!allowSelfLoops && source != target)) && (allowParallel || !idSet + .contains(edge.getLongId()))) { edgeList.add(edge); c++; idSet.add(edge.getLongId()); @@ -182,10 +194,11 @@ public static BasicGraphStore.BasicEdge[] generateBasicEdgeList(BasicGraphStore. int targetId = r.nextInt(nodeCount); BasicGraphStore.BasicNode source = nodeStore.get(String.valueOf(sourceId)); BasicGraphStore.BasicNode target = nodeStore.get(String.valueOf(targetId)); - BasicGraphStore.BasicEdge edge = new BasicGraphStore.BasicEdge(String.valueOf(c), source, target, type, - 1.0, directed); - if (!leafs.contains(sourceId) && !leafs.contains(targetId) && (allowSelfLoops || (!allowSelfLoops && source != target)) && !idSet - .contains(edge.getStringId())) { + BasicGraphStore.BasicEdge edge = new BasicGraphStore.BasicEdge(String.valueOf(c), source, target, type, 1.0, + directed); + if (!leafs.contains(sourceId) && !leafs + .contains(targetId) && (allowSelfLoops || (!allowSelfLoops && source != target)) && !idSet + .contains(edge.getStringId())) { edgeList.add(edge); c++; idSet.add(edge.getStringId()); @@ -218,9 +231,9 @@ public static EdgeImpl[] generateMixedEdgeList(NodeStore nodeStore, int edgeCoun NodeImpl source = nodeStore.get(sourceId); NodeImpl target = nodeStore.get(targetId); EdgeImpl edge = new EdgeImpl(String.valueOf(c), source, target, type, 1.0, false); - if ((allowSelfLoops || (!allowSelfLoops && source != target)) && !idSet.contains(EdgeStore - .getLongId(edge.source, edge.target, true)) && !idSet.contains(EdgeStore - .getLongId(edge.target, edge.source, true))) { + if ((allowSelfLoops || (!allowSelfLoops && source != target)) && !idSet + .contains(EdgeStore.getLongId(edge.source, edge.target, true)) && !idSet + .contains(EdgeStore.getLongId(edge.target, edge.source, true))) { edgeList.add(edge); c++; idSet.add(edge.getLongId()); @@ -240,8 +253,8 @@ public static BasicGraphStore.BasicEdge[] generateBasicMixedEdgeList(BasicGraphS int targetId = r.nextInt(nodeCount); BasicGraphStore.BasicNode source = nodeStore.get(String.valueOf(sourceId)); BasicGraphStore.BasicNode target = nodeStore.get(String.valueOf(targetId)); - BasicGraphStore.BasicEdge edge = new BasicGraphStore.BasicEdge(String.valueOf(c), source, target, type, - 1.0, true); + BasicGraphStore.BasicEdge edge = new BasicGraphStore.BasicEdge(String.valueOf(c), source, target, type, 1.0, + true); if ((allowSelfLoops || (!allowSelfLoops && source != target)) && !idSet.contains(edge.getStringId())) { edgeList.add(edge); c++; @@ -253,11 +266,11 @@ public static BasicGraphStore.BasicEdge[] generateBasicMixedEdgeList(BasicGraphS int targetId = r.nextInt(nodeCount); BasicGraphStore.BasicNode source = nodeStore.get(String.valueOf(sourceId)); BasicGraphStore.BasicNode target = nodeStore.get(String.valueOf(targetId)); - BasicGraphStore.BasicEdge edge = new BasicGraphStore.BasicEdge(String.valueOf(c), source, target, type, - 1.0, false); - if ((allowSelfLoops || (!allowSelfLoops && source != target)) && !idSet.contains(BasicEdgeStore - .getStringId(edge.source, edge.target, true)) && !idSet.contains(BasicEdgeStore - .getStringId(edge.target, edge.source, true))) { + BasicGraphStore.BasicEdge edge = new BasicGraphStore.BasicEdge(String.valueOf(c), source, target, type, 1.0, + false); + if ((allowSelfLoops || (!allowSelfLoops && source != target)) && !idSet + .contains(BasicEdgeStore.getStringId(edge.source, edge.target, true)) && !idSet + .contains(BasicEdgeStore.getStringId(edge.target, edge.source, true))) { edgeList.add(edge); c++; idSet.add(edge.getStringId()); @@ -315,7 +328,7 @@ public static int[] distributeTypeCounts(int typeCount, int edgeCount) { res[i] = edgeCount - total; assert res[i] > 0; } else { - res[i] = (int) (ratio[i] / sum); + res[i] = (int) (edgeCount * ratio[i] / sum); total += res[i]; } } @@ -375,9 +388,20 @@ public static NodeImpl[] generateNodeList(int nodeCount) { } public static NodeImpl[] generateNodeList(int nodeCount, GraphStore graphStore) { + return generateNodeList(nodeCount, graphStore, null); + } + + public static NodeImpl[] generateNodeList(int nodeCount, GraphStore graphStore, Rect2D area) { NodeImpl[] nodes = new NodeImpl[nodeCount]; + Random random = new Random(); for (int i = 0; i < nodeCount; i++) { NodeImpl node = new NodeImpl(String.valueOf(i), graphStore); + if (area != null) { + float x = area.minX + random.nextFloat() * (area.maxX - area.minX); + float y = area.minY + random.nextFloat() * (area.maxY - area.minY); + node.setPosition(x, y); + node.setSize(1.0f); + } nodes[i] = node; } return nodes; @@ -396,12 +420,16 @@ public static GraphStore generateTinyGraphStore() { return generateTinyGraphStore(GraphStoreConfiguration.DEFAULT_TIME_REPRESENTATION); } + public static GraphStore generateEmptyGraphStore() { + return generateEmptyGraphStore(GraphStoreConfiguration.DEFAULT_TIME_REPRESENTATION); + } + public static GraphStore generateTinyUndirectedGraphStore() { - GraphModelImpl graphModel = new GraphModelImpl(new Configuration()); + GraphModelImpl graphModel = new GraphModelImpl(Configuration.builder().build()); GraphStore graphStore = graphModel.store; Node n1 = graphStore.factory.newNode("1"); Node n2 = graphStore.factory.newNode("2"); - graphStore.addAllNodes(Arrays.asList(new Node[] { n1, n2 })); + graphStore.addAllNodes(Arrays.asList(n1, n2)); Edge e0 = graphStore.factory.newEdge("0", n1, n2, EdgeTypeStore.NULL_LABEL, 1.0, false); graphStore.addEdge(e0); return graphStore; @@ -419,18 +447,64 @@ public static GraphStore generateTinyGraphStore(Configuration configuration) { return graphStore; } + public static GraphStore generateEmptyGraphStore(Configuration configuration) { + GraphModelImpl graphModel = new GraphModelImpl(configuration); + return graphModel.store; + } + + public static GraphStore generateEmptyGraphStore(TimeRepresentation timeRepresentation) { + Configuration config = Configuration.builder().timeRepresentation(timeRepresentation).build(); + return generateEmptyGraphStore(config); + } + public static GraphStore generateTinyGraphStore(TimeRepresentation timeRepresentation) { - Configuration config = new Configuration(); - config.setTimeRepresentation(timeRepresentation); + Configuration config = Configuration.builder().timeRepresentation(timeRepresentation).build(); return generateTinyGraphStore(config); } + public static GraphStore generateTinyGraphStoreWithSelfLoop(Configuration configuration) { + GraphModelImpl graphModel = new GraphModelImpl(configuration); + GraphStore graphStore = graphModel.store; + NodeImpl n1 = new NodeImpl("1", graphStore); + EdgeImpl e = new EdgeImpl("0", graphStore, n1, n1, EdgeTypeStore.NULL_LABEL, 1.0, true); + graphStore.addNode(n1); + graphStore.addEdge(e); + return graphStore; + } + + public static GraphStore generateTinyGraphStoreWithSelfLoop() { + return generateTinyGraphStoreWithSelfLoop(Configuration.builder().build()); + } + + public static GraphStore generateTinyGraphStoreWithMutualEdge() { + GraphModelImpl graphModel = new GraphModelImpl(); + GraphStore graphStore = graphModel.store; + NodeImpl n1 = new NodeImpl("1", graphStore); + NodeImpl n2 = new NodeImpl("2", graphStore); + EdgeImpl e1 = new EdgeImpl("0", graphStore, n1, n2, EdgeTypeStore.NULL_LABEL, 1.0, true); + EdgeImpl e2 = new EdgeImpl("1", graphStore, n2, n1, EdgeTypeStore.NULL_LABEL, 1.0, true); + graphStore.addNode(n1); + graphStore.addNode(n2); + graphStore.addEdge(e1); + graphStore.addEdge(e2); + return graphStore; + } + public static GraphStore generateSmallGraphStore() { + return generateSmallGraphStore(true); + } + + public static GraphStore generateSmallGraphStoreWithoutSelfLoop() { + return generateSmallGraphStore(false); + } + + private static GraphStore generateSmallGraphStore(boolean allowSelfLoops) { int edgeCount = 100; GraphStore graphStore = new GraphModelImpl().store; - NodeImpl[] nodes = generateNodeList(Math.max((int) Math.ceil(Math.sqrt(edgeCount * 2)), (int) (edgeCount / 10.0)), graphStore); + NodeImpl[] nodes = generateNodeList(Math + .max((int) Math.ceil(Math.sqrt(edgeCount * 2)), (int) (edgeCount / 10.0)), graphStore); graphStore.addAllNodes(Arrays.asList(nodes)); - EdgeImpl[] edges = generateEdgeList(graphStore.nodeStore, edgeCount, 0, true, true, false); + EdgeImpl[] edges = generateEdgeList(graphStore.nodeStore, edgeCount, 0, true, allowSelfLoops, false); graphStore.addAllEdges(Arrays.asList(edges)); return graphStore; } @@ -442,7 +516,8 @@ public static GraphStore generateSmallMixedGraphStore() { public static GraphStore generateSmallMixedGraphStore(int type) { int edgeCount = 100; GraphStore graphStore = new GraphModelImpl().store; - NodeImpl[] nodes = generateNodeList(Math.max((int) Math.ceil(Math.sqrt(edgeCount * 2)), (int) (edgeCount / 10.0)), graphStore); + NodeImpl[] nodes = generateNodeList(Math + .max((int) Math.ceil(Math.sqrt(edgeCount * 2)), (int) (edgeCount / 10.0)), graphStore); graphStore.addAllNodes(Arrays.asList(nodes)); EdgeImpl[] edges = generateMixedEdgeList(graphStore.nodeStore, edgeCount, type, true); graphStore.addAllEdges(Arrays.asList(edges)); @@ -452,7 +527,8 @@ public static GraphStore generateSmallMixedGraphStore(int type) { public static GraphStore generateSmallMultiTypeGraphStore() { int edgeCount = 100; GraphStore graphStore = new GraphModelImpl().store; - NodeImpl[] nodes = generateNodeList(Math.max((int) Math.ceil(Math.sqrt(edgeCount * 2)), (int) (edgeCount / 10.0)), graphStore); + NodeImpl[] nodes = generateNodeList(Math + .max((int) Math.ceil(Math.sqrt(edgeCount * 2)), (int) (edgeCount / 10.0)), graphStore); graphStore.addAllNodes(Arrays.asList(nodes)); EdgeImpl[] edges = generateMultiTypeEdgeList(graphStore.nodeStore, edgeCount, 3, true, true); graphStore.addAllEdges(Arrays.asList(edges)); @@ -462,7 +538,8 @@ public static GraphStore generateSmallMultiTypeGraphStore() { public static GraphStore generateSmallUndirectedGraphStore() { int edgeCount = 100; GraphStore graphStore = new GraphModelImpl().store; - NodeImpl[] nodes = generateNodeList(Math.max((int) Math.ceil(Math.sqrt(edgeCount * 2)), (int) (edgeCount / 10.0)), graphStore); + NodeImpl[] nodes = generateNodeList(Math + .max((int) Math.ceil(Math.sqrt(edgeCount * 2)), (int) (edgeCount / 10.0)), graphStore); graphStore.addAllNodes(Arrays.asList(nodes)); EdgeImpl[] edges = generateEdgeList(graphStore.nodeStore, edgeCount, 0, false, true, false); graphStore.addAllEdges(Arrays.asList(edges)); @@ -474,7 +551,7 @@ public static GraphStore generateLargeGraphStore() { NodeImpl[] nodes = generateLargeNodeList(); graphStore.addAllNodes(Arrays.asList(nodes)); - EdgeImpl[] edges = generateLargeEdgeList(); + EdgeImpl[] edges = generateEdgeList(graphStore.nodeStore, GraphStoreConfiguration.EDGESTORE_BLOCK_SIZE * 3 + (int) (GraphStoreConfiguration.EDGESTORE_BLOCK_SIZE / 3.0), 0, true, true, false); graphStore.addAllEdges(Arrays.asList(edges)); return graphStore; } diff --git a/src/test/java/org/gephi/graph/impl/GraphGeneratorTest.java b/src/test/java/org/gephi/graph/impl/GraphGeneratorTest.java new file mode 100644 index 00000000..49bd4731 --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/GraphGeneratorTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import org.gephi.graph.api.Edge; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class GraphGeneratorTest { + + @Test + public void testGenerateLargeGraphStoreEdgesReferenceRealNodes() { + GraphStore graphStore = GraphGenerator.generateLargeGraphStore(); + + // Edges must reference the same node objects registered in the store's own nodeStore, not just + // objects that happen to carry a matching storeId - otherwise removeNode()'s cascade-edge-removal + // (which walks the real node's own adjacency links) silently fails to find and remove them. + for (Edge edge : graphStore.edgeStore) { + EdgeImpl e = (EdgeImpl) edge; + Assert.assertSame(graphStore.nodeStore.get(e.source.storeId), e.source); + Assert.assertSame(graphStore.nodeStore.get(e.target.storeId), e.target); + } + } +} diff --git a/src/test/java/org/gephi/graph/impl/GraphLockImplTest.java b/src/test/java/org/gephi/graph/impl/GraphLockImplTest.java new file mode 100644 index 00000000..2b65bd97 --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/GraphLockImplTest.java @@ -0,0 +1,236 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class GraphLockImplTest { + + @Test + public void testReadUnlockAll() { + GraphLockImpl lock = new GraphLockImpl(); + lock.readLock(); + lock.readLock(); + Assert.assertEquals(lock.readWriteLock.getReadHoldCount(), 2); + lock.readUnlockAll(); + Assert.assertEquals(lock.readWriteLock.getReadLockCount(), 0); + } + + @Test + public void testWriteLockBeforeReadLock() { + GraphLockImpl lock = new GraphLockImpl(); + lock.writeLock(); + lock.readLock(); + lock.readLock(); + } + + @Test(expectedExceptions = IllegalMonitorStateException.class) + public void testWriteLockAfterReadLock() { + GraphLockImpl lock = new GraphLockImpl(); + lock.readLock(); + lock.writeLock(); + } + + @Test + public void testCheckHoldWriteLock() { + GraphLockImpl lock = new GraphLockImpl(); + lock.writeLock(); + lock.checkHoldWriteLock(); + } + + @Test(expectedExceptions = IllegalMonitorStateException.class) + public void testCheckHoldWriteLockFail() { + GraphLockImpl lock = new GraphLockImpl(); + lock.checkHoldWriteLock(); + } + + @Test + public void testHoldersCount() { + GraphLockImpl lock = new GraphLockImpl(); + lock.readLock(); + Assert.assertEquals(lock.getReadHoldCount(), 1); + lock.readUnlock(); + Assert.assertEquals(lock.getReadHoldCount(), 0); + lock.writeLock(); + Assert.assertEquals(lock.getWriteHoldCount(), 1); + lock.writeUnlock(); + Assert.assertEquals(lock.getWriteHoldCount(), 0); + + } + + // --- Timed acquisition --- + + @Test + public void testTryWriteLockAcquiresWhenFree() throws InterruptedException { + GraphLockImpl lock = new GraphLockImpl(); + Assert.assertTrue(lock.tryWriteLock(1, TimeUnit.SECONDS)); + Assert.assertEquals(lock.getWriteHoldCount(), 1); + lock.writeUnlock(); + } + + @Test + public void testTryWriteLockTimesOutWhileAnotherThreadHoldsRead() throws InterruptedException { + GraphLockImpl lock = new GraphLockImpl(); + CountDownLatch acquired = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + Thread reader = holdLockOnThread(lock, false, acquired, release); + acquired.await(); + Assert.assertFalse(lock.tryWriteLock(100, TimeUnit.MILLISECONDS)); + Assert.assertEquals(lock.getWriteHoldCount(), 0); + release.countDown(); + reader.join(); + Assert.assertTrue(lock.tryWriteLock(1, TimeUnit.SECONDS)); + lock.writeUnlock(); + } + + @Test(expectedExceptions = IllegalMonitorStateException.class) + public void testTryWriteLockWhileHoldingReadLock() throws InterruptedException { + GraphLockImpl lock = new GraphLockImpl(); + lock.readLock(); + lock.tryWriteLock(1, TimeUnit.SECONDS); + } + + @Test + public void testTryReadLockAcquiresWhenFree() throws InterruptedException { + GraphLockImpl lock = new GraphLockImpl(); + Assert.assertTrue(lock.tryReadLock(1, TimeUnit.SECONDS)); + Assert.assertEquals(lock.getReadHoldCount(), 1); + lock.readUnlock(); + } + + @Test + public void testTryReadLockTimesOutWhileAnotherThreadHoldsWrite() throws InterruptedException { + GraphLockImpl lock = new GraphLockImpl(); + CountDownLatch acquired = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + Thread writer = holdLockOnThread(lock, true, acquired, release); + acquired.await(); + Assert.assertFalse(lock.tryReadLock(100, TimeUnit.MILLISECONDS)); + Assert.assertEquals(lock.getReadHoldCount(), 0); + release.countDown(); + writer.join(); + Assert.assertTrue(lock.tryReadLock(1, TimeUnit.SECONDS)); + lock.readUnlock(); + } + + @Test + public void testTryWriteLockPropagatesInterrupt() throws InterruptedException { + GraphLockImpl lock = new GraphLockImpl(); + CountDownLatch acquired = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + Thread reader = holdLockOnThread(lock, false, acquired, release); + acquired.await(); + AtomicBoolean interrupted = new AtomicBoolean(false); + CountDownLatch waiting = new CountDownLatch(1); + Thread writer = new Thread(() -> { + try { + waiting.countDown(); + lock.tryWriteLock(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + interrupted.set(true); + } + }); + writer.start(); + waiting.await(); + writer.interrupt(); + writer.join(5000); + Assert.assertFalse(writer.isAlive()); + Assert.assertTrue(interrupted.get()); + Assert.assertEquals(lock.getWriteHoldCount(), 0); + release.countDown(); + reader.join(); + } + + // --- Diagnostics --- + + @Test + public void testGetQueueLengthReportsQueuedWriter() throws InterruptedException { + GraphLockImpl lock = new GraphLockImpl(); + lock.readLock(); + Assert.assertEquals(lock.getQueueLength(), 0); + Thread writer = new Thread(() -> { + lock.writeLock(); + lock.writeUnlock(); + }); + writer.start(); + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5); + while (lock.getQueueLength() == 0 && System.nanoTime() < deadline) { + Thread.sleep(5); + } + Assert.assertEquals(lock.getQueueLength(), 1); + lock.readUnlock(); + writer.join(); + Assert.assertEquals(lock.getQueueLength(), 0); + } + + @Test + public void testGetReadLockCountCountsHoldsAcrossThreads() throws InterruptedException { + GraphLockImpl lock = new GraphLockImpl(); + Assert.assertEquals(lock.getReadLockCount(), 0); + lock.readLock(); + CountDownLatch acquired = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + Thread reader = holdLockOnThread(lock, false, acquired, release); + acquired.await(); + Assert.assertEquals(lock.getReadLockCount(), 2); + Assert.assertEquals(lock.getReadHoldCount(), 1); + release.countDown(); + reader.join(); + Assert.assertEquals(lock.getReadLockCount(), 1); + lock.readUnlock(); + Assert.assertEquals(lock.getReadLockCount(), 0); + } + + @Test + public void testIsWriteLocked() { + GraphLockImpl lock = new GraphLockImpl(); + Assert.assertFalse(lock.isWriteLocked()); + lock.writeLock(); + Assert.assertTrue(lock.isWriteLocked()); + lock.writeUnlock(); + Assert.assertFalse(lock.isWriteLocked()); + } + + // Holds the read (or write) lock on a background thread until released, so the test thread can observe contention. + private static Thread holdLockOnThread(GraphLockImpl lock, boolean write, CountDownLatch acquired, CountDownLatch release) { + Thread t = new Thread(() -> { + if (write) { + lock.writeLock(); + } else { + lock.readLock(); + } + acquired.countDown(); + try { + release.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + if (write) { + lock.writeUnlock(); + } else { + lock.readUnlock(); + } + } + }); + t.setDaemon(true); + t.start(); + return t; + } +} diff --git a/store/src/test/java/org/gephi/graph/impl/GraphModelTest.java b/src/test/java/org/gephi/graph/impl/GraphModelTest.java similarity index 61% rename from store/src/test/java/org/gephi/graph/impl/GraphModelTest.java rename to src/test/java/org/gephi/graph/impl/GraphModelTest.java index d5d7f9cf..12a95e1a 100644 --- a/store/src/test/java/org/gephi/graph/impl/GraphModelTest.java +++ b/src/test/java/org/gephi/graph/impl/GraphModelTest.java @@ -16,6 +16,7 @@ package org.gephi.graph.impl; import java.io.IOException; +import java.time.ZoneId; import java.util.Arrays; import org.gephi.graph.api.Column; import org.gephi.graph.api.Configuration; @@ -34,7 +35,6 @@ import org.testng.Assert; import org.testng.annotations.Test; import org.gephi.graph.api.TimeIndex; -import org.joda.time.DateTimeZone; import org.gephi.graph.api.TimeRepresentation; import org.gephi.graph.api.types.IntervalDoubleMap; import org.gephi.graph.api.types.IntervalSet; @@ -116,6 +116,32 @@ public void testGetEdgeTypeLabels() { GraphModelImpl graphModel = new GraphModelImpl(); graphModel.addEdgeType("foo"); Assert.assertEquals(graphModel.getEdgeTypeLabels(), new Object[] { null, "foo" }); + Assert.assertEquals(graphModel.getEdgeTypeLabels(true), new Object[] { null, "foo" }); + } + + @Test + public void testGetEdgeTypeLabelsEmpty() { + GraphModelImpl graphModel = new GraphModelImpl(); + graphModel.addEdgeType("foo"); + Assert.assertEquals(graphModel.getEdgeTypeLabels(false), new Object[] {}); + } + + @Test + public void testGetEdgeTypeLabelsNotEmpty() { + GraphModelImpl graphModel = GraphGenerator.generateTinyGraphStore().graphModel; + Assert.assertEquals(graphModel.getEdgeTypeLabels(false), new Object[] { null }); + } + + @Test + public void testGetEdgeTypeLabelsNotEmptyMultiGraph() { + GraphModelImpl graphModel = GraphGenerator.generateTinyGraphStore().graphModel; + Node n1 = graphModel.store.getNode("1"); + Node n2 = graphModel.store.getNode("2"); + graphModel.addEdgeType("bar"); + int type = graphModel.addEdgeType("foo"); + Edge e1 = graphModel.store.factory.newEdge("1", n1, n2, type, 1.0, false); + graphModel.store.addEdge(e1); + Assert.assertEquals(graphModel.getEdgeTypeLabels(false), new Object[] { null, "foo" }); } @Test @@ -176,6 +202,203 @@ public void testCreateViewCustom() { Assert.assertFalse(view.isEdgeView()); } + @Test + public void testCreateViewWithPredicates() { + GraphModelImpl graphModel = new GraphModelImpl(); + GraphView view = graphModel.createView(n -> true, e -> true); + Assert.assertNotNull(view); + Assert.assertSame(view.getGraphModel(), graphModel); + Assert.assertTrue(view.isNodeView()); + Assert.assertTrue(view.isEdgeView()); + } + + @Test + public void testCreateViewWithNodePredicateOnly() { + GraphModelImpl graphModel = new GraphModelImpl(); + GraphView view = graphModel.createView(n -> true, null); + Assert.assertNotNull(view); + Assert.assertTrue(view.isNodeView()); + Assert.assertFalse(view.isEdgeView()); + } + + @Test + public void testCreateViewWithEdgePredicateOnly() { + GraphModelImpl graphModel = new GraphModelImpl(); + GraphView view = graphModel.createView(null, e -> true); + Assert.assertNotNull(view); + Assert.assertFalse(view.isNodeView()); + Assert.assertTrue(view.isEdgeView()); + } + + @Test + public void testCreateViewWithBothPredicatesNull() { + GraphModelImpl graphModel = new GraphModelImpl(); + GraphView view = graphModel.createView(null, null); + Assert.assertNotNull(view); + Assert.assertFalse(view.isNodeView()); + Assert.assertFalse(view.isEdgeView()); + } + + @Test + public void testCreateViewWithNodePredicateFiltering() { + GraphModelImpl graphModel = new GraphModelImpl(); + Table table = graphModel.getNodeTable(); + Column col = table.addColumn("value", Integer.class); + + Node n1 = graphModel.factory().newNode("1"); + n1.setAttribute(col, 10); + Node n2 = graphModel.factory().newNode("2"); + n2.setAttribute(col, 20); + Node n3 = graphModel.factory().newNode("3"); + n3.setAttribute(col, 30); + graphModel.getStore().addAllNodes(Arrays.asList(new Node[] { n1, n2, n3 })); + + // Create view with predicate that filters nodes with value > 15 + GraphView view = graphModel.createView(n -> { + Integer value = (Integer) n.getAttribute(col); + return value != null && value > 15; + }, null); + + Graph subgraph = graphModel.getGraph(view); + Assert.assertEquals(subgraph.getNodeCount(), 2); + Assert.assertTrue(subgraph.contains(n2)); + Assert.assertTrue(subgraph.contains(n3)); + Assert.assertFalse(subgraph.contains(n1)); + } + + @Test + public void testCreateViewWithEdgePredicateFiltering() { + GraphModelImpl graphModel = new GraphModelImpl(); + Table table = graphModel.getEdgeTable(); + Column col = table.addColumn("weight_custom", Double.class); + + Node n1 = graphModel.factory().newNode("1"); + Node n2 = graphModel.factory().newNode("2"); + Node n3 = graphModel.factory().newNode("3"); + graphModel.getStore().addAllNodes(Arrays.asList(new Node[] { n1, n2, n3 })); + + Edge e1 = graphModel.factory().newEdge(n1, n2); + e1.setAttribute(col, 1.0); + Edge e2 = graphModel.factory().newEdge(n2, n3); + e2.setAttribute(col, 5.0); + Edge e3 = graphModel.factory().newEdge(n1, n3); + e3.setAttribute(col, 10.0); + graphModel.getStore().addAllEdges(Arrays.asList(new Edge[] { e1, e2, e3 })); + + // Create view with predicate that filters edges with weight >= 5.0 + GraphView view = graphModel.createView(null, e -> { + Double weight = (Double) e.getAttribute(col); + return weight != null && weight >= 5.0; + }); + + Graph subgraph = graphModel.getGraph(view); + Assert.assertEquals(subgraph.getNodeCount(), 3); // All nodes included + Assert.assertEquals(subgraph.getEdgeCount(), 2); + Assert.assertTrue(subgraph.contains(e2)); + Assert.assertTrue(subgraph.contains(e3)); + Assert.assertFalse(subgraph.contains(e1)); + } + + @Test + public void testCreateViewWithBothPredicatesFiltering() { + GraphModelImpl graphModel = new GraphModelImpl(); + Table nodeTable = graphModel.getNodeTable(); + Table edgeTable = graphModel.getEdgeTable(); + Column nodeCol = nodeTable.addColumn("active", Boolean.class); + Column edgeCol = edgeTable.addColumn("strength", Double.class); + + Node n1 = graphModel.factory().newNode("1"); + n1.setAttribute(nodeCol, true); + Node n2 = graphModel.factory().newNode("2"); + n2.setAttribute(nodeCol, false); + Node n3 = graphModel.factory().newNode("3"); + n3.setAttribute(nodeCol, true); + graphModel.getStore().addAllNodes(Arrays.asList(new Node[] { n1, n2, n3 })); + + Edge e1 = graphModel.factory().newEdge(n1, n2); + e1.setAttribute(edgeCol, 0.5); + Edge e2 = graphModel.factory().newEdge(n2, n3); + e2.setAttribute(edgeCol, 0.8); + Edge e3 = graphModel.factory().newEdge(n1, n3); + e3.setAttribute(edgeCol, 0.3); + graphModel.getStore().addAllEdges(Arrays.asList(new Edge[] { e1, e2, e3 })); + + // Create view with both predicates + GraphView view = graphModel.createView(n -> Boolean.TRUE.equals(n.getAttribute(nodeCol)), e -> { + Double strength = (Double) e.getAttribute(edgeCol); + return strength != null && strength > 0.4; + }); + + Graph subgraph = graphModel.getGraph(view); + + Assert.assertEquals(subgraph.getNodeCount(), 2); // n1 and n3 + Assert.assertTrue(subgraph.contains(n1)); + Assert.assertTrue(subgraph.contains(n3)); + Assert.assertFalse(subgraph.contains(n2)); + + // No edges should be included: + // - e1 has n2 which is not in node view (active=false) + // - e2 has n2 which is not in node view (active=false) + // - e3 connects n1 and n3 (both in view) but strength 0.3 fails edge filter + Assert.assertEquals(subgraph.getEdgeCount(), 0); + Assert.assertFalse(subgraph.contains(e1)); + Assert.assertFalse(subgraph.contains(e2)); + Assert.assertFalse(subgraph.contains(e3)); + } + + @Test + public void testCreateViewWithBothPredicatesFilteringWithMatchingEdges() { + GraphModelImpl graphModel = new GraphModelImpl(); + Table nodeTable = graphModel.getNodeTable(); + Table edgeTable = graphModel.getEdgeTable(); + Column nodeCol = nodeTable.addColumn("category", String.class); + Column edgeCol = edgeTable.addColumn("score", Double.class); + + Node n1 = graphModel.factory().newNode("1"); + n1.setAttribute(nodeCol, "A"); + Node n2 = graphModel.factory().newNode("2"); + n2.setAttribute(nodeCol, "B"); + Node n3 = graphModel.factory().newNode("3"); + n3.setAttribute(nodeCol, "A"); + Node n4 = graphModel.factory().newNode("4"); + n4.setAttribute(nodeCol, "A"); + graphModel.getStore().addAllNodes(Arrays.asList(new Node[] { n1, n2, n3, n4 })); + + Edge e1 = graphModel.factory().newEdge(n1, n2); + e1.setAttribute(edgeCol, 5.0); + Edge e2 = graphModel.factory().newEdge(n1, n3); + e2.setAttribute(edgeCol, 3.0); + Edge e3 = graphModel.factory().newEdge(n3, n4); + e3.setAttribute(edgeCol, 8.0); + Edge e4 = graphModel.factory().newEdge(n2, n4); + e4.setAttribute(edgeCol, 1.0); + graphModel.getStore().addAllEdges(Arrays.asList(new Edge[] { e1, e2, e3, e4 })); + + // Create view: nodes with category "A" AND edges with score > 2.0 + GraphView view = graphModel.createView(n -> "A".equals(n.getAttribute(nodeCol)), e -> { + Double score = (Double) e.getAttribute(edgeCol); + return score != null && score > 2.0; + }); + + Graph subgraph = graphModel.getGraph(view); + + // Nodes: n1, n3, n4 (all category "A") + Assert.assertEquals(subgraph.getNodeCount(), 3); + Assert.assertTrue(subgraph.contains(n1)); + Assert.assertTrue(subgraph.contains(n3)); + Assert.assertTrue(subgraph.contains(n4)); + Assert.assertFalse(subgraph.contains(n2)); + + // Edges: only e2 (n1-n3, score 3.0) and e3 (n3-n4, score 8.0) + // e1 excluded: n2 not in view + // e4 excluded: n2 not in view + Assert.assertEquals(subgraph.getEdgeCount(), 2); + Assert.assertFalse(subgraph.contains(e1)); + Assert.assertTrue(subgraph.contains(e2)); + Assert.assertTrue(subgraph.contains(e3)); + Assert.assertFalse(subgraph.contains(e4)); + } + @Test public void testCopyView() { GraphModelImpl graphModel = new GraphModelImpl(); @@ -223,11 +446,11 @@ public void testSetTimeFormat() { @Test public void testSetTimeZone() { GraphModelImpl graphModel = new GraphModelImpl(); - Assert.assertEquals(graphModel.getTimeZone(), DateTimeZone.UTC);// Default - graphModel.setTimeZone(DateTimeZone.forID("-02:00")); - Assert.assertEquals(graphModel.getTimeZone(), DateTimeZone.forID("-02:00")); - graphModel.setTimeZone(DateTimeZone.UTC); - Assert.assertEquals(graphModel.getTimeZone(), DateTimeZone.UTC); + Assert.assertEquals(graphModel.getTimeZone(), ZoneId.of("UTC"));// Default + graphModel.setTimeZone(ZoneId.of("-02:00")); + Assert.assertEquals(graphModel.getTimeZone(), ZoneId.of("-02:00")); + graphModel.setTimeZone(ZoneId.of("UTC")); + Assert.assertEquals(graphModel.getTimeZone(), ZoneId.of("UTC")); } @Test @@ -318,6 +541,14 @@ public void testGetNodeIndex() { Index index = graphModel.getNodeIndex(); Assert.assertEquals(index.count(col, "bar"), 1); + Assert.assertSame(graphModel.getElementIndex(table), index); + } + + @Test + public void testGetNodeIndexWithIndexConfigDisabled() { + GraphModelImpl graphModel = new GraphModelImpl(Configuration.builder().enableIndexNodes(false).build()); + Assert.assertNotNull(graphModel.getNodeIndex()); + Assert.assertNotNull(graphModel.getNodeIndex().getColumnIndex(graphModel.defaultColumns().nodeId())); } @Test @@ -334,6 +565,7 @@ public void testGetNodeIndexInView() { Index index = graphModel.getNodeIndex(view); Assert.assertEquals(index.count(col, "bar"), 1); + Assert.assertSame(graphModel.getElementIndex(table, view), index); } @Test @@ -350,6 +582,40 @@ public void testGetEdgeIndex() { Index index = graphModel.getEdgeIndex(); Assert.assertEquals(index.count(col, "bar"), 1); + Assert.assertSame(graphModel.getElementIndex(table), index); + } + + @Test + public void testIndexVersionWithIndexedColumn() { + GraphModelImpl graphModel = new GraphModelImpl(); + Table table = graphModel.getNodeTable(); + Column col = table.addColumn("foo", String.class); + Index index = graphModel.getNodeIndex(); + int version = index.getColumnIndex(col).getVersion(); + Node n1 = graphModel.factory().newNode("1"); + n1.setAttribute(col, "bar"); + graphModel.getStore().addNode(n1); + Assert.assertTrue(index.getColumnIndex(col).getVersion() > version); + } + + @Test + public void testIndexVersionWithNoIndexedColumn() { + GraphModelImpl graphModel = new GraphModelImpl(); + Table table = graphModel.getNodeTable(); + Column col = table.addColumn("foo", "foo", Integer.class, Origin.DATA, null, false); + Index index = graphModel.getNodeIndex(); + int version = index.getColumnIndex(col).getVersion(); + Node n1 = graphModel.factory().newNode("1"); + n1.setAttribute(col, 42); + graphModel.getStore().addNode(n1); + Assert.assertTrue(index.getColumnIndex(col).getVersion() > version); + } + + @Test + public void testGetEdgeIndexWithIndexConfigDisabled() { + GraphModelImpl graphModel = new GraphModelImpl(Configuration.builder().enableIndexEdges(false).build()); + Assert.assertNotNull(graphModel.getEdgeIndex()); + Assert.assertNotNull(graphModel.getEdgeIndex().getColumnIndex(graphModel.defaultColumns().edgeId())); } @Test @@ -369,6 +635,7 @@ public void testGetEdgeIndexInView() { Index index = graphModel.getEdgeIndex(view); Assert.assertEquals(index.count(col, "bar"), 1); + Assert.assertSame(graphModel.getElementIndex(table, view), index); } @Test @@ -383,6 +650,12 @@ public void testGetNodeTimestampIndex() { Assert.assertEquals(index.getMaxTimestamp(), 1.0); } + @Test + public void testGetNodeTimeIndexWithIndexConfigDisabled() { + GraphModelImpl graphModel = new GraphModelImpl(Configuration.builder().enableIndexTime(false).build()); + Assert.assertNull(graphModel.getNodeTimeIndex()); + } + @Test public void testGetEdgeTimestampIndex() { GraphModelImpl graphModel = new GraphModelImpl(); @@ -397,6 +670,12 @@ public void testGetEdgeTimestampIndex() { Assert.assertEquals(index.getMaxTimestamp(), 1.0); } + @Test + public void testGetEdgeTimeIndexWithIndexConfigDisabled() { + GraphModelImpl graphModel = new GraphModelImpl(Configuration.builder().enableIndexTime(false).build()); + Assert.assertNull(graphModel.getEdgeTimeIndex()); + } + @Test public void testGetNodeTimestampIndexInView() { GraphModelImpl graphModel = new GraphModelImpl(); @@ -452,16 +731,15 @@ public void testSerializationReadWithoutVersionHeader() throws IOException { @Test public void testGetConfiguration() { - Configuration config = new Configuration(); - config.setNodeIdType(Long.class); + Configuration config = Configuration.builder().nodeIdType(Long.class).build(); GraphModelImpl graphModelImpl = new GraphModelImpl(config); Assert.assertEquals(graphModelImpl.getConfiguration(), config); } @Test + @SuppressWarnings("deprecated") public void testGetConfigurationCopy() { - Configuration config = new Configuration(); - config.setNodeIdType(Long.class); + Configuration config = Configuration.builder().nodeIdType(Long.class).build(); GraphModelImpl graphModelImpl = new GraphModelImpl(config); Assert.assertEquals(graphModelImpl.getConfiguration(), config); config.setNodeIdType(Float.class); @@ -470,12 +748,9 @@ public void testGetConfigurationCopy() { @Test public void testSetConfigurationIntervals() { - Configuration config = new Configuration(); + Configuration config = Configuration.builder().nodeIdType(Integer.class).edgeIdType(Byte.class) + .timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModelImpl = new GraphModelImpl(config); - config.setNodeIdType(Integer.class); - config.setEdgeIdType(Byte.class); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); - graphModelImpl.setConfiguration(config); Assert.assertEquals(graphModelImpl.getConfiguration(), config); Assert.assertEquals(graphModelImpl.getNodeTable().getColumn("id").getTypeClass(), Integer.class); Assert.assertEquals(graphModelImpl.getEdgeTable().getColumn("id").getTypeClass(), Byte.class); @@ -490,20 +765,13 @@ public void testSetConfigurationIntervals() { Assert.assertEquals(graphModelImpl.getEdgeTable().getColumn(GraphStoreConfiguration.EDGE_WEIGHT_INDEX) .getTypeClass(), Double.class); - config.setEdgeWeightType(IntervalDoubleMap.class); - graphModelImpl.setConfiguration(config); - Assert.assertEquals(graphModelImpl.getEdgeTable().getColumn(GraphStoreConfiguration.EDGE_WEIGHT_INDEX) - .getTypeClass(), IntervalDoubleMap.class); } @Test public void testSetConfigurationTimestamps() { - Configuration config = new Configuration(); + Configuration config = Configuration.builder().nodeIdType(Integer.class).edgeIdType(Byte.class) + .timeRepresentation(TimeRepresentation.TIMESTAMP).build(); GraphModelImpl graphModelImpl = new GraphModelImpl(config); - config.setNodeIdType(Integer.class); - config.setEdgeIdType(Byte.class); - config.setTimeRepresentation(TimeRepresentation.TIMESTAMP); - graphModelImpl.setConfiguration(config); Assert.assertEquals(graphModelImpl.getConfiguration(), config); Assert.assertEquals(graphModelImpl.getNodeTable().getColumn("id").getTypeClass(), Integer.class); Assert.assertEquals(graphModelImpl.getEdgeTable().getColumn("id").getTypeClass(), Byte.class); @@ -518,87 +786,46 @@ public void testSetConfigurationTimestamps() { Assert.assertEquals(graphModelImpl.getEdgeTable().getColumn(GraphStoreConfiguration.EDGE_WEIGHT_INDEX) .getTypeClass(), Double.class); - config.setEdgeWeightType(TimestampDoubleMap.class); - graphModelImpl.setConfiguration(config); - Assert.assertEquals(graphModelImpl.getEdgeTable().getColumn(GraphStoreConfiguration.EDGE_WEIGHT_INDEX) - .getTypeClass(), TimestampDoubleMap.class); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadEdgeWeightTypeConfigurationIntervals() { - Configuration config = new Configuration(); - GraphModelImpl graphModelImpl = new GraphModelImpl(config); - config.setEdgeWeightType(TimestampDoubleMap.class); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); - graphModelImpl.setConfiguration(config); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL) + .edgeWeightType(TimestampDoubleMap.class).build(); + new GraphModelImpl(config); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadEdgeWeightTypeConfigurationTimestamps() { - Configuration config = new Configuration(); - GraphModelImpl graphModelImpl = new GraphModelImpl(config); - config.setEdgeWeightType(IntervalDoubleMap.class); - config.setTimeRepresentation(TimeRepresentation.TIMESTAMP); - graphModelImpl.setConfiguration(config); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void testSetConfigurationWithNodes() { - GraphModelImpl graphModelImpl = new GraphModelImpl(); - graphModelImpl.store.addNode(graphModelImpl.factory().newNode()); - graphModelImpl.setConfiguration(new Configuration()); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void testSetConfigurationWithGraphAttributes() { - GraphModelImpl graphModelImpl = new GraphModelImpl(); - graphModelImpl.getGraph().setAttribute("foo", "bar"); - graphModelImpl.setConfiguration(new Configuration()); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.TIMESTAMP) + .edgeWeightType(IntervalDoubleMap.class).build(); + new GraphModelImpl(config); } - @Test(expectedExceptions = IllegalStateException.class) - public void testSetConfigurationWithNodeColumns() { + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testSetConfiguration() { GraphModelImpl graphModelImpl = new GraphModelImpl(); - graphModelImpl.store.nodeTable.addColumn("foo", Integer.class); - graphModelImpl.setConfiguration(new Configuration()); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void testSetConfigurationWithEdgeColumns() { - GraphModelImpl graphModelImpl = new GraphModelImpl(); - graphModelImpl.store.edgeTable.addColumn("foo", Integer.class); - graphModelImpl.setConfiguration(new Configuration()); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void testSetConfigurationWithEdgeType() { - GraphModelImpl graphModelImpl = new GraphModelImpl(); - graphModelImpl.store.edgeTypeStore.addType("foo"); - graphModelImpl.setConfiguration(new Configuration()); + graphModelImpl.setConfiguration(Configuration.builder().build()); } @Test public void testSetConfigurationEdgeWeightColumnFalse() { - GraphModelImpl graphModelImpl = new GraphModelImpl(); + Configuration config = Configuration.builder().edgeWeightColumn(false).build(); + GraphModelImpl graphModelImpl = new GraphModelImpl(config); - Configuration config = new Configuration(); - config.setEdgeWeightColumn(Boolean.FALSE); - graphModelImpl.setConfiguration(config); Assert.assertFalse(graphModelImpl.store.edgeTable.hasColumn("weight")); - Assert.assertNotEquals(graphModelImpl.store.edgeTable.addColumn("foo", Integer.class).getIndex(), GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + Assert.assertNotEquals(graphModelImpl.store.edgeTable.addColumn("foo", Integer.class) + .getIndex(), GraphStoreConfiguration.EDGE_WEIGHT_INDEX); } @Test public void testSetConfigurationEdgeWeightColumnTrue() { - Configuration config = new Configuration(); - config.setEdgeWeightColumn(Boolean.FALSE); + Configuration config = Configuration.builder().edgeWeightColumn(true).build(); GraphModelImpl graphModelImpl = new GraphModelImpl(config); - config = new Configuration(); - config.setEdgeWeightColumn(Boolean.TRUE); - graphModelImpl.setConfiguration(config); Assert.assertTrue(graphModelImpl.store.edgeTable.hasColumn("weight")); - Assert.assertEquals(graphModelImpl.store.edgeTable.getColumn("weight").getIndex(), GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + Assert.assertEquals(graphModelImpl.store.edgeTable.getColumn("weight") + .getIndex(), GraphStoreConfiguration.EDGE_WEIGHT_INDEX); } @Test @@ -638,34 +865,6 @@ public void testNodeAttributesAddAndRemoveColumns2() { n1.setAttribute(col2, "test"); } - @Test - public void testReplaceEdgeWeightColumnUpdatesConfiguration() { - GraphModelImpl graphModel = new GraphModelImpl(); - Graph graph = graphModel.getGraph(); - - Table table = graphModel.getEdgeTable(); - - Node n1 = graphModel.factory().newNode("1"); - Node n2 = graphModel.factory().newNode("2"); - Edge edge = graphModel.factory().newEdge(n1, n2); - graph.addNode(n1); - graph.addNode(n2); - graph.addEdge(edge); - - Assert.assertTrue(graphModel.getConfiguration().getEdgeWeightColumn()); - Assert.assertEquals(graphModel.getConfiguration().getEdgeWeightType(), Double.class); - Assert.assertFalse(edge.hasDynamicWeight()); - - table.removeColumn(GraphStoreConfiguration.EDGE_WEIGHT_COLUMN_ID); - Assert.assertFalse(graphModel.getConfiguration().getEdgeWeightColumn()); - - table.addColumn(GraphStoreConfiguration.EDGE_WEIGHT_COLUMN_ID, IntervalDoubleMap.class, Origin.PROPERTY); - - Assert.assertTrue(graphModel.getConfiguration().getEdgeWeightColumn()); - Assert.assertEquals(graphModel.getConfiguration().getEdgeWeightType(), IntervalDoubleMap.class); - Assert.assertTrue(edge.hasDynamicWeight()); - } - @Test public void testRemoveColumnWithNodes() { GraphModelImpl graphModel = new GraphModelImpl(); @@ -689,9 +888,27 @@ public void testNodeAttributesAddAndClearColumns() { n1.setAttribute(col1, "bar"); graphModel.getStore().addNode(n1); - ((TableImpl) table).store.clear(); Column col2 = table.addColumn("foo2", String.class); Assert.assertNull(n1.getAttribute(col2)); } + + @Test + public void testDefaultColumns() { + GraphModelImpl graphModel = new GraphModelImpl(); + GraphModel.DefaultColumns defaultColumns = graphModel.defaultColumns(); + Assert.assertNotNull(defaultColumns); + + Assert.assertNotNull(defaultColumns.degree()); + Assert.assertNotNull(defaultColumns.inDegree()); + Assert.assertNotNull(defaultColumns.outDegree()); + Assert.assertNotNull(defaultColumns.nodeId()); + Assert.assertNotNull(defaultColumns.edgeId()); + Assert.assertNotNull(defaultColumns.nodeLabel()); + Assert.assertNotNull(defaultColumns.edgeLabel()); + Assert.assertNotNull(defaultColumns.nodeTimeSet()); + Assert.assertNotNull(defaultColumns.edgeTimeSet()); + Assert.assertNotNull(defaultColumns.edgeType()); + + } } diff --git a/store/src/test/java/org/gephi/graph/impl/GraphObserverTest.java b/src/test/java/org/gephi/graph/impl/GraphObserverTest.java similarity index 89% rename from store/src/test/java/org/gephi/graph/impl/GraphObserverTest.java rename to src/test/java/org/gephi/graph/impl/GraphObserverTest.java index 1e1b15a1..b2066b1e 100644 --- a/store/src/test/java/org/gephi/graph/impl/GraphObserverTest.java +++ b/src/test/java/org/gephi/graph/impl/GraphObserverTest.java @@ -389,6 +389,57 @@ public void testDiffRemoveAllNodes() { Assert.assertSame(diff.getAddedNodes(), NodeIterable.EMPTY); } + @Test + public void testDiffRemovedNodeNotReportedTwice() { + GraphStore store = GraphGenerator.generateSmallGraphStore(); + GraphObserverImpl graphObserver = store.createGraphObserver(store, true); + graphObserver.hasGraphChanged(); + + Node removed = store.getNodes().toArray()[0]; + store.removeNode(removed); + + // First diff: removal is reported + graphObserver.hasGraphChanged(); + GraphDiff diff1 = graphObserver.getDiff(); + Assert.assertEquals(diff1.getRemovedNodes().toArray().length, 1); + + // Make an unrelated change so a second diff is triggered + store.addNode(store.factory.newNode("extra")); + + // Second diff: the already-reported removal must NOT appear again + graphObserver.hasGraphChanged(); + GraphDiff diff2 = graphObserver.getDiff(); + for (Node n : diff2.getRemovedNodes()) { + Assert.assertNotSame(n, removed); + } + } + + @Test + public void testDiffRemovedEdgeNotReportedTwice() { + GraphStore store = GraphGenerator.generateSmallGraphStore(); + GraphObserverImpl graphObserver = store.createGraphObserver(store, true); + graphObserver.hasGraphChanged(); + + Edge removed = store.getEdges().toArray()[0]; + store.removeEdge(removed); + + // First diff: removal is reported + graphObserver.hasGraphChanged(); + GraphDiff diff1 = graphObserver.getDiff(); + Assert.assertEquals(diff1.getRemovedEdges().toArray().length, 1); + + // Make an unrelated change so a second diff is triggered + Node[] ns = store.getNodes().toArray(); + store.addEdge(store.factory.newEdge("extra", ns[0], ns[1], 0, 1.0, true)); + + // Second diff: the already-reported removal must NOT appear again + graphObserver.hasGraphChanged(); + GraphDiff diff2 = graphObserver.getDiff(); + for (Edge e : diff2.getRemovedEdges()) { + Assert.assertNotSame(e, removed); + } + } + @Test public void testDiffReplaceNode() { GraphStore store = GraphGenerator.generateSmallGraphStore(); diff --git a/store/src/test/java/org/gephi/graph/impl/GraphStoreTest.java b/src/test/java/org/gephi/graph/impl/GraphStoreTest.java similarity index 73% rename from store/src/test/java/org/gephi/graph/impl/GraphStoreTest.java rename to src/test/java/org/gephi/graph/impl/GraphStoreTest.java index 501e0b9b..b08abda0 100644 --- a/store/src/test/java/org/gephi/graph/impl/GraphStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/GraphStoreTest.java @@ -21,12 +21,14 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.awt.Color; import java.util.Arrays; -import java.util.HashSet; +import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.Spliterator; import org.gephi.graph.api.Column; import org.gephi.graph.api.ColumnIterable; +import org.gephi.graph.api.Configuration; import org.gephi.graph.api.DirectedSubgraph; import org.gephi.graph.api.Edge; import org.gephi.graph.api.EdgeIterable; @@ -448,6 +450,11 @@ public Interval[] getIntervals() { throw new UnsupportedOperationException("Not supported yet."); } + @Override + public Interval getTimeBounds() { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override public Iterable getAttributes(Column column) { throw new UnsupportedOperationException("Not supported yet."); @@ -466,7 +473,7 @@ public void testAddEdge() { NodeImpl[] nodes = GraphGenerator.generateNodeList(2); graphStore.addAllNodes(Arrays.asList(nodes)); - EdgeImpl edge = new EdgeImpl("0", nodes[0], nodes[1], 0, 1.0, true); + EdgeImpl edge = new EdgeImpl("0", graphStore, nodes[0], nodes[1], 0, 1.0, true); boolean a = graphStore.addEdge(edge); boolean b = graphStore.addEdge(edge); @@ -478,6 +485,81 @@ public void testAddEdge() { Assert.assertTrue(c); } + @Test + public void testAddEdgeWithSameType() { + GraphStore graphStore = new GraphStore(); + NodeImpl[] nodes = GraphGenerator.generateNodeList(2); + graphStore.addAllNodes(Arrays.asList(nodes)); + + EdgeImpl edge1 = new EdgeImpl("0", graphStore, nodes[0], nodes[1], 0, 1.0, true); + EdgeImpl edge2 = new EdgeImpl("1", graphStore, nodes[0], nodes[1], 0, 1.0, true); + boolean a = graphStore.addEdge(edge1); + boolean b = graphStore.addEdge(edge2); + + Assert.assertTrue(a); + Assert.assertTrue(b); + + Assert.assertTrue(graphStore.contains(edge1)); + Assert.assertTrue(graphStore.contains(edge2)); + } + + @Test + public void testAddEdgeWithSameTypeWithoutParallel() { + Configuration configuration = Configuration.builder().enableParallelEdgesSameType(false).build(); + GraphStore graphStore = new GraphStore(null, new ConfigurationImpl(configuration)); + NodeImpl[] nodes = GraphGenerator.generateNodeList(2); + graphStore.addAllNodes(Arrays.asList(nodes)); + + EdgeImpl edge1 = new EdgeImpl("0", graphStore, nodes[0], nodes[1], 0, 1.0, true); + EdgeImpl edge2 = new EdgeImpl("1", graphStore, nodes[0], nodes[1], 0, 1.0, true); + Assert.assertTrue(graphStore.addEdge(edge1)); + Assert.assertFalse(graphStore.addEdge(edge2)); + + Assert.assertTrue(graphStore.contains(edge1)); + Assert.assertFalse(graphStore.contains(edge2)); + } + + @Test + public void testAddEdgeTypeRegistration() { + GraphStore graphStore = new GraphStore(); + NodeImpl[] nodes = GraphGenerator.generateNodeList(2); + graphStore.addAllNodes(Arrays.asList(nodes)); + + EdgeTypeStore typeStore = graphStore.edgeTypeStore; + Assert.assertFalse(typeStore.contains(1)); + + EdgeImpl edge = new EdgeImpl("0", graphStore, nodes[0], nodes[1], 1, 1.0, true); + graphStore.addEdge(edge); + + Assert.assertTrue(typeStore.contains(1)); + Assert.assertTrue(typeStore.contains("1")); + } + + @Test + public void testAddAllEdges() { + GraphStore graphStore = new GraphStore(); + NodeImpl[] nodes = GraphGenerator.generateNodeList(2); + graphStore.addAllNodes(Arrays.asList(nodes)); + + EdgeImpl edge = new EdgeImpl("0", graphStore, nodes[0], nodes[1], 0, 1.0, true); + graphStore.addAllEdges(Collections.singletonList(edge)); + + Assert.assertTrue(graphStore.contains(edge)); + } + + @Test + public void testAddAllEdgesTypeRegistration() { + GraphStore graphStore = new GraphStore(); + NodeImpl[] nodes = GraphGenerator.generateNodeList(2); + graphStore.addAllNodes(Arrays.asList(nodes)); + + EdgeImpl edge = new EdgeImpl("0", graphStore, nodes[0], nodes[1], 1, 1.0, true); + graphStore.addAllEdges(Collections.singletonList(edge)); + + Assert.assertTrue(graphStore.edgeTypeStore.contains(1)); + Assert.assertTrue(graphStore.edgeTypeStore.contains("1")); + } + @Test public void testRemoveNodeWithEdges() { GraphStore graphStore = new GraphStore(); @@ -537,6 +619,24 @@ public void testGetNode() { Assert.assertFalse(graphStore.hasNode("bar")); } + @Test + public void testGetNodeByStoreId() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + for (Node node : graphStore.getNodes().toArray()) { + Assert.assertNotNull(graphStore.getNodeByStoreId(node.getStoreId())); + } + } + + @Test + public void testGetNodeByStoreIdIsNull() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + for (Node node : graphStore.getNodes().toArray()) { + int storeId = node.getStoreId(); + graphStore.removeNode(node); + Assert.assertNull(graphStore.getNodeByStoreId(storeId)); + } + } + @Test public void testGetEdge() { GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); @@ -546,6 +646,23 @@ public void testGetEdge() { Assert.assertFalse(graphStore.hasEdge("bar")); } + @Test + public void testGetEdgeByStoreId() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + for (Edge edge : graphStore.getEdges().toArray()) { + Assert.assertNotNull(graphStore.getEdgeByStoreId(edge.getStoreId())); + } + } + + @Test + public void testGetEdgeByStoreIdIsNull() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + Edge toRemove = graphStore.getEdge("0"); + int storeId = toRemove.getStoreId(); + graphStore.removeEdge(toRemove); + Assert.assertNull(graphStore.getEdgeByStoreId(storeId)); + } + @Test public void testGetMutualEdge() { GraphStore graphStore = new GraphStore(); @@ -583,6 +700,30 @@ public void testGetEdges() { testEdgeIterable(edgeIterable, edges); } + @Test + public void testGetEdgesDefaultType() { + GraphStore graphStore = new GraphStore(); + NodeStore nodeStore = GraphGenerator.generateNodeStore(5); + EdgeImpl[] edges = GraphGenerator.generateEdgeList(nodeStore, 4, 0, true, true, false); + graphStore.addAllEdges(Arrays.asList(edges)); + EdgeIterable edgeIterable = graphStore.getEdges(0); + testEdgeIterable(edgeIterable, edges); + } + + @Test + public void testGetEdgesByType() { + GraphStore graphStore = new GraphStore(); + NodeStore nodeStore = GraphGenerator.generateNodeStore(15); + EdgeImpl[] edges = GraphGenerator.generateMultiTypeEdgeList(nodeStore, 4, 3, true, true); + graphStore.addAllEdges(Arrays.asList(edges)); + testEdgeIterable(graphStore.getEdges(0), Arrays.stream(edges).filter(e -> e.getType() == 0) + .toArray(EdgeImpl[]::new)); + testEdgeIterable(graphStore.getEdges(1), Arrays.stream(edges).filter(e -> e.getType() == 1) + .toArray(EdgeImpl[]::new)); + testEdgeIterable(graphStore.getEdges(2), Arrays.stream(edges).filter(e -> e.getType() == 2) + .toArray(EdgeImpl[]::new)); + } + @Test public void testGetNodeEdges() { GraphStore graphStore = new GraphStore(); @@ -591,8 +732,8 @@ public void testGetNodeEdges() { graphStore.addAllEdges(Arrays.asList(edges)); for (EdgeImpl e : edges) { - testEdgeIterable(graphStore.getEdges(e.source, e.target), new EdgeImpl[] { e }); - testEdgeIterable(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e }); + testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target), new EdgeImpl[] { e }); + testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e }); } } @@ -604,8 +745,8 @@ public void testGetNodeEdgesUnusedEdgeType() { graphStore.addAllEdges(Arrays.asList(edges)); for (EdgeImpl e : edges) { - testEdgeIterable(graphStore.getEdges(e.source, e.target), new EdgeImpl[] {}); - testEdgeIterable(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e }); + testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target), new EdgeImpl[] {}); + testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e }); } } @@ -614,8 +755,8 @@ public void testGetNodeEdgesMixed() { GraphStore graphStore = GraphGenerator.generateSmallMixedGraphStore(); for (EdgeImpl e : graphStore.edgeStore.toArray()) { - testEdgeIterable(graphStore.getEdges(e.source, e.target), new EdgeImpl[] { e }); - testEdgeIterable(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e }); + testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target), new EdgeImpl[] { e }); + testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e }); } } @@ -624,8 +765,8 @@ public void testGetNodeEdgesMixedUnusedEdgeType() { GraphStore graphStore = GraphGenerator.generateSmallMixedGraphStore(2); for (EdgeImpl e : graphStore.edgeStore.toArray()) { - testEdgeIterable(graphStore.getEdges(e.source, e.target), new EdgeImpl[] {}); - testEdgeIterable(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e }); + testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target), new EdgeImpl[] {}); + testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e }); } } @@ -658,6 +799,34 @@ public void testRemoveEdge() { Assert.assertFalse(graphStore.removeEdge(edge)); } + @Test + public void testRemoveNodeAlreadyRemoved() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + Node node = graphStore.getNodes().toArray()[0]; + + Assert.assertTrue(graphStore.removeNode(node)); + Assert.assertFalse(graphStore.contains(node)); + Assert.assertFalse(graphStore.removeNode(node)); + } + + @Test + public void testRemoveNodeNeverAdded() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + Node detached = graphStore.factory.newNode("detached"); + + Assert.assertFalse(graphStore.removeNode(detached)); + } + + @Test + public void testRemoveAllNodesWithAlreadyRemoved() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + Node[] nodes = graphStore.getNodes().toArray(); + + Assert.assertTrue(graphStore.removeNode(nodes[0])); + Assert.assertTrue(graphStore.removeAllNodes(Arrays.asList(nodes))); + Assert.assertEquals(graphStore.getNodeCount(), 0); + } + @Test public void testRemoveAllNode() { GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); @@ -682,6 +851,40 @@ public void testRemoveAllEdges() { } } + @Test + public void testRetainNodes() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + Node[] nodes = graphStore.getNodes().toArray(); + Assert.assertFalse(graphStore.retainNodes(Arrays.asList(nodes))); + Assert.assertEquals(graphStore.getNodeCount(), nodes.length); + + Assert.assertTrue(graphStore.retainNodes(Collections.EMPTY_LIST)); + Assert.assertEquals(graphStore.getNodeCount(), 0); + + graphStore = GraphGenerator.generateSmallGraphStore(); + nodes = graphStore.getNodes().toArray(); + graphStore.retainNodes(Collections.singletonList(nodes[0])); + Assert.assertEquals(graphStore.getNodeCount(), 1); + Assert.assertTrue(graphStore.contains(nodes[0])); + } + + @Test + public void testRetainEdges() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + Edge[] edges = graphStore.getEdges().toArray(); + Assert.assertFalse(graphStore.retainEdges(Arrays.asList(edges))); + Assert.assertEquals(graphStore.getEdgeCount(), edges.length); + + Assert.assertTrue(graphStore.retainEdges(Collections.EMPTY_LIST)); + Assert.assertEquals(graphStore.getEdgeCount(), 0); + + graphStore = GraphGenerator.generateSmallGraphStore(); + edges = graphStore.getEdges().toArray(); + graphStore.retainEdges(Collections.singletonList(edges[0])); + Assert.assertEquals(graphStore.getEdgeCount(), 1); + Assert.assertTrue(graphStore.contains(edges[0])); + } + @Test public void testGetOpposite() { GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); @@ -724,6 +927,38 @@ public void testClearEdges() { Assert.assertEquals(graphStore.getEdgeCount(), 0); } + @Test + public void testClearUpdatesViews() { + // Regression test: clear() did not propagate to views, leaving them with + // stale bit-vectors and counts. + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewImpl view = graphStore.viewStore.createView(); + view.fill(); + + Assert.assertEquals(view.getNodeCount(), graphStore.getNodeCount()); + Assert.assertEquals(view.getEdgeCount(), graphStore.getEdgeCount()); + + graphStore.clear(); + + Assert.assertEquals(view.getNodeCount(), 0); + Assert.assertEquals(view.getEdgeCount(), 0); + } + + @Test + public void testClearEdgesUpdatesViews() { + // Regression test: clearEdges() did not propagate to views. + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewImpl view = graphStore.viewStore.createView(); + view.fill(); + + Assert.assertTrue(view.getEdgeCount() > 0); + + graphStore.clearEdges(); + + Assert.assertEquals(view.getEdgeCount(), 0); + Assert.assertEquals(view.getNodeCount(), graphStore.getNodeCount()); + } + @Test public void testClearEdgesByType() { GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); @@ -866,21 +1101,34 @@ public void testEdgeIterableToArray() { Assert.assertEquals(edgeCollection, expected); } + @Test + public void testVersion() { + GraphStore graphStore = new GraphStore(); + int version = graphStore.getVersion(); + NodeImpl[] nodes = GraphGenerator.generateNodeList(2); + graphStore.addNode(nodes[0]); + Assert.assertNotEquals(graphStore.getVersion(), version); + graphStore.addNode(nodes[1]); + version = graphStore.getVersion(); + graphStore.addEdge(graphStore.factory.newEdge(nodes[0], nodes[1])); + Assert.assertNotEquals(graphStore.getVersion(), version); + } + // UTILITY private void testNodeIterable(NodeIterable iterable, NodeImpl[] nodes) { - Set nodeSet = new HashSet<>(iterable.toCollection()); - for (NodeImpl n : nodes) { - Assert.assertTrue(nodeSet.remove(n)); - } - Assert.assertEquals(nodeSet.size(), 0); + Assert.assertEquals(iterable.toArray(), nodes); + Assert.assertEquals(iterable.stream().toArray(Node[]::new), nodes); + Assert.assertEquals(iterable.parallelStream().toArray(Node[]::new), nodes); } private void testEdgeIterable(EdgeIterable iterable, EdgeImpl[] edges) { - Set edgeSet = new HashSet<>(iterable.toCollection()); - for (EdgeImpl n : edges) { - Assert.assertTrue(edgeSet.remove(n)); - } - Assert.assertEquals(edgeSet.size(), 0); + testEdgeIterableWithoutParallel(iterable, edges); + Assert.assertEquals(iterable.parallelStream().toArray(Edge[]::new), edges); + } + + private void testEdgeIterableWithoutParallel(EdgeIterable iterable, EdgeImpl[] edges) { + Assert.assertEquals(iterable.toArray(), edges); + Assert.assertEquals(iterable.stream().toArray(Edge[]::new), edges); } private void testBasicStoreEquals(GraphStore graphStore, BasicGraphStore basicGraphStore) { @@ -1004,4 +1252,36 @@ private void testNodeSets(NodeIterable n1, NodeIterable n2) { } Assert.assertEquals(s2.size(), 0); } + + @Test + public void testGetEdgesTypeSpliteratorMatches() { + GraphStore gs = GraphGenerator.generateSmallMultiTypeGraphStore(); + Edge[] edges = gs.getEdges().toArray(); + for (int i = 0; i < 3; i++) { + Spliterator sp = gs.getEdges(i).spliterator(); + int finalI = i; + sp.forEachRemaining(e -> Assert.assertEquals(finalI, e.getType())); + } + } + + // @Test + // public void testGetSelfLoopsSpliteratorMatches() { + // GraphStore gs = new GraphStore(); + // NodeImpl[] nodes = GraphGenerator.generateSmallNodeList(); + // gs.addAllNodes(Arrays.asList(nodes)); + // EdgeImpl[] edges = new EdgeImpl[] { + // GraphGenerator.generateSelfLoop(0, true), + // GraphGenerator.generateSelfLoop(1, false) + // }; + // for (EdgeImpl e : edges) { + // e.source = nodes[0]; + // e.target = nodes[0]; + // gs.addEdge(e); + // } + // Set ids = new HashSet<>(); + // gs.getSelfLoops().spliterator().forEachRemaining(e -> ids.add(e.getId())); + // for (EdgeImpl e : edges) { + // Assert.assertTrue(ids.contains(e.getId())); + // } + // } } diff --git a/store/src/test/java/org/gephi/graph/impl/GraphVersionTest.java b/src/test/java/org/gephi/graph/impl/GraphVersionTest.java similarity index 100% rename from store/src/test/java/org/gephi/graph/impl/GraphVersionTest.java rename to src/test/java/org/gephi/graph/impl/GraphVersionTest.java diff --git a/store/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java b/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java similarity index 70% rename from store/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java rename to src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java index 61aea4dd..a3352163 100644 --- a/store/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java +++ b/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java @@ -15,15 +15,18 @@ */ package org.gephi.graph.impl; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import it.unimi.dsi.fastutil.objects.ObjectSet; +import java.util.Arrays; +import java.util.Collections; import java.util.Random; +import java.util.stream.Collectors; +import org.gephi.graph.api.Configuration; import org.gephi.graph.api.DirectedSubgraph; import org.gephi.graph.api.Edge; -import org.gephi.graph.api.Element; -import org.gephi.graph.api.ElementIterable; +import org.gephi.graph.api.EdgeIterable; import org.gephi.graph.api.Interval; import org.gephi.graph.api.Node; +import org.gephi.graph.api.NodeIterable; +import org.gephi.graph.api.Rect2D; import org.gephi.graph.api.UndirectedSubgraph; import org.testng.Assert; import org.testng.annotations.Test; @@ -101,8 +104,8 @@ public void testUndirectedAdd() { Assert.assertTrue(a); Assert.assertFalse(b); Assert.assertTrue(graph.contains(e)); - boolean mutualToIgnore = graphStore.edgeStore.isUndirectedToIgnore((EdgeImpl) e); - if (!mutualToIgnore) { + EdgeImpl mutualEdge = graphStore.edgeStore.getMutualEdge(e); + if (!(mutualEdge != null && !e.isSelfLoop() && graph.contains(mutualEdge))) { Assert.assertEquals(graph.getEdgeCount(), ++count); } } @@ -234,8 +237,8 @@ public void testUndirectedRemove() { Assert.assertTrue(a); Assert.assertFalse(b); Assert.assertFalse(graph.contains(e)); - boolean mutualToIgnore = graphStore.edgeStore.isUndirectedToIgnore((EdgeImpl) e); - if (!mutualToIgnore) { + EdgeImpl mutualEdge = graphStore.edgeStore.getMutualEdge(e); + if (!(mutualEdge != null && !e.isSelfLoop() && graph.contains(mutualEdge))) { Assert.assertEquals(graph.getEdgeCount(), --count); } } @@ -356,6 +359,53 @@ public void testUndirectedRemoveNodesFirst() { Assert.assertEquals(graph.getEdgeCount(), 0); } + @Test + public void testDirectedRetainNodes() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStoreWithoutSelfLoop(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + DirectedSubgraph graph = store.getDirectedGraph(view); + view.fill(); + + Assert.assertFalse(graph.retainNodes(graphStore.getNodes().toCollection())); + Assert.assertEquals(graph.getNodeCount(), graphStore.getNodeCount()); + + Assert.assertTrue(graph.retainNodes(Collections.EMPTY_LIST)); + Assert.assertEquals(graph.getNodeCount(), 0); + + view.fill(); + Edge edge = graphStore.getEdges().toArray()[0]; + Assert.assertTrue(graph.retainNodes(Arrays.asList(edge.getSource(), edge.getTarget()))); + Assert.assertEquals(graph.getNodeCount(), 2); + Assert.assertTrue(graph.contains(edge.getSource())); + Assert.assertTrue(graph.contains(edge.getTarget())); + Assert.assertTrue(graph.contains(edge)); + } + + @Test + public void testDirectedRetainEdges() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStoreWithoutSelfLoop(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + DirectedSubgraph graph = store.getDirectedGraph(view); + view.fill(); + + Assert.assertFalse(graph.retainEdges(graphStore.getEdges().toCollection())); + Assert.assertEquals(graph.getEdgeCount(), graphStore.getEdgeCount()); + + Assert.assertTrue(graph.retainEdges(Collections.EMPTY_LIST)); + Assert.assertEquals(graph.getEdgeCount(), 0); + + view.fill(); + Edge edge = graphStore.getEdges().toArray()[0]; + Assert.assertTrue(graph.retainEdges(Collections.singletonList(edge))); + Assert.assertEquals(graph.getNodeCount(), graphStore.getNodeCount()); + Assert.assertEquals(graph.getEdgeCount(), 1); + Assert.assertTrue(graph.contains(edge)); + } + @Test public void testDirectedClearEdges() { GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); @@ -371,9 +421,7 @@ public void testDirectedClearEdges() { for (Edge e : graphStore.getEdges()) { Assert.assertFalse(graph.contains(e)); } - for (int i = 0; i < graphStore.edgeTypeStore.length; i++) { - Assert.assertEquals(graph.getEdgeCount(i), 0); - } + Assert.assertEquals(graph.getEdgeCount(0), 0); } @Test @@ -391,9 +439,7 @@ public void testUndirectedClearEdges() { for (Edge e : graphStore.getEdges()) { Assert.assertFalse(graph.contains(e)); } - for (int i = 0; i < graphStore.edgeTypeStore.length; i++) { - Assert.assertEquals(graph.getEdgeCount(i), 0); - } + Assert.assertEquals(graph.getEdgeCount(0), 0); } @Test @@ -415,9 +461,8 @@ public void testDirectedClear() { for (Node n : graphStore.getNodes()) { Assert.assertFalse(graph.contains(n)); } - for (int i = 0; i < graphStore.edgeTypeStore.length; i++) { - Assert.assertEquals(graph.getEdgeCount(i), 0); - } + + Assert.assertEquals(graph.getEdgeCount(0), 0); } @Test @@ -439,9 +484,7 @@ public void testUndirectedClear() { for (Node n : graphStore.getNodes()) { Assert.assertFalse(graph.contains(n)); } - for (int i = 0; i < graphStore.edgeTypeStore.length; i++) { - Assert.assertEquals(graph.getEdgeCount(i), 0); - } + Assert.assertEquals(graph.getEdgeCount(0), 0); } @Test @@ -455,26 +498,26 @@ public void testDirectedIterators() { DirectedSubgraph graph = store.getDirectedGraph(view); GraphStore copyGraphStore = convertToStore(view); - Assert.assertTrue(isIterablesEqual(graph.getNodes(), copyGraphStore.getNodes())); - Assert.assertTrue(isIterablesEqual(graph.getEdges(), copyGraphStore.getEdges())); - Assert.assertTrue(isIterablesEqual(graph.getSelfLoops(), copyGraphStore.getSelfLoops())); + assertIterablesEqual(graph.getNodes(), copyGraphStore.getNodes()); + assertIterablesEqual(graph.getEdges(), copyGraphStore.getEdges()); + assertIterablesEqual(graph.getSelfLoops(), copyGraphStore.getSelfLoops()); for (Node n : graph.getNodes()) { Node m = copyGraphStore.getNode(n.getId()); - Assert.assertTrue(isIterablesEqual(graph.getEdges(n), copyGraphStore.getEdges(m))); - Assert.assertTrue(isIterablesEqual(graph.getInEdges(n), copyGraphStore.getInEdges(m))); - Assert.assertTrue(isIterablesEqual(graph.getOutEdges(n), copyGraphStore.getOutEdges(m))); - Assert.assertTrue(isIterablesEqual(graph.getNeighbors(n), copyGraphStore.getNeighbors(m))); - Assert.assertTrue(isIterablesEqual(graph.getSuccessors(n), copyGraphStore.getSuccessors(m))); - Assert.assertTrue(isIterablesEqual(graph.getPredecessors(n), copyGraphStore.getPredecessors(m))); + assertIterablesEqual(graph.getEdges(n), copyGraphStore.getEdges(m)); + assertIterablesEqual(graph.getInEdges(n), copyGraphStore.getInEdges(m)); + assertIterablesEqual(graph.getOutEdges(n), copyGraphStore.getOutEdges(m)); + assertIterablesEqual(graph.getNeighbors(n), copyGraphStore.getNeighbors(m)); + assertIterablesEqual(graph.getSuccessors(n), copyGraphStore.getSuccessors(m)); + assertIterablesEqual(graph.getPredecessors(n), copyGraphStore.getPredecessors(m)); for (int i = 0; i < typeCount; i++) { - Assert.assertTrue(isIterablesEqual(graph.getEdges(n, i), copyGraphStore.getEdges(m, i))); - Assert.assertTrue(isIterablesEqual(graph.getInEdges(n, i), copyGraphStore.getInEdges(m, i))); - Assert.assertTrue(isIterablesEqual(graph.getOutEdges(n, i), copyGraphStore.getOutEdges(m, i))); - Assert.assertTrue(isIterablesEqual(graph.getNeighbors(n, i), copyGraphStore.getNeighbors(m, i))); - Assert.assertTrue(isIterablesEqual(graph.getSuccessors(n, i), copyGraphStore.getSuccessors(m, i))); - Assert.assertTrue(isIterablesEqual(graph.getPredecessors(n, i), copyGraphStore.getPredecessors(m, i))); + assertIterablesEqual(graph.getEdges(n, i), copyGraphStore.getEdges(m, i)); + assertIterablesEqual(graph.getInEdges(n, i), copyGraphStore.getInEdges(m, i)); + assertIterablesEqual(graph.getOutEdges(n, i), copyGraphStore.getOutEdges(m, i)); + assertIterablesEqual(graph.getNeighbors(n, i), copyGraphStore.getNeighbors(m, i)); + assertIterablesEqual(graph.getSuccessors(n, i), copyGraphStore.getSuccessors(m, i)); + assertIterablesEqual(graph.getPredecessors(n, i), copyGraphStore.getPredecessors(m, i)); } } } @@ -490,21 +533,18 @@ public void testUndirectedIterators() { UndirectedSubgraph graph = store.getUndirectedGraph(view); GraphStore copyGraphStore = convertToStore(view); - Assert.assertTrue(isIterablesEqual(graph.getNodes(), copyGraphStore.undirectedDecorator.getNodes())); - Assert.assertTrue(isIterablesEqual(graph.getEdges(), copyGraphStore.undirectedDecorator.getEdges())); - Assert.assertTrue(isIterablesEqual(graph.getSelfLoops(), copyGraphStore.undirectedDecorator.getSelfLoops())); + assertIterablesEqual(graph.getNodes(), copyGraphStore.undirectedDecorator.getNodes()); + assertIterablesEqual(graph.getEdges(), copyGraphStore.undirectedDecorator.getEdges()); + assertIterablesEqual(graph.getSelfLoops(), copyGraphStore.undirectedDecorator.getSelfLoops()); for (Node n : graph.getNodes()) { Node m = copyGraphStore.getNode(n.getId()); - Assert.assertTrue(isIterablesEqual(graph.getEdges(n), copyGraphStore.undirectedDecorator.getEdges(m))); - Assert.assertTrue(isIterablesEqual(graph.getNeighbors(n), copyGraphStore.undirectedDecorator - .getNeighbors(m))); + assertIterablesEqual(graph.getEdges(n), copyGraphStore.undirectedDecorator.getEdges(m)); + assertIterablesEqual(graph.getNeighbors(n), copyGraphStore.undirectedDecorator.getNeighbors(m)); for (int i = 0; i < typeCount; i++) { - Assert.assertTrue(isIterablesEqual(graph.getEdges(n, i), copyGraphStore.undirectedDecorator - .getEdges(m, i))); - Assert.assertTrue(isIterablesEqual(graph.getNeighbors(n, i), copyGraphStore.undirectedDecorator - .getNeighbors(m, i))); + assertIterablesEqual(graph.getEdges(n, i), copyGraphStore.undirectedDecorator.getEdges(m, i)); + assertIterablesEqual(graph.getNeighbors(n, i), copyGraphStore.undirectedDecorator.getNeighbors(m, i)); } } } @@ -746,6 +786,7 @@ public void testIsIncident() { Edge edge = graphStore.factory.newEdge("edge", n1, n1, EdgeTypeStore.NULL_LABEL, 1.0, true); graphStore.addEdge(edge); + view.addEdge(edge); // Explicitly add edge to view Assert.assertTrue(graph.isIncident(edge, graph.getEdge("0"))); } @@ -847,17 +888,200 @@ public void testIntersection() { Assert.assertTrue(graph1.contains(n2)); } + @Test + public void testGetBoundariesEmptyView() { + GraphStore graphStore = GraphGenerator.generateEmptyGraphStore(getSpatialConfig()); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + DirectedSubgraph graph = store.getDirectedGraph(view); + + Assert.assertEquals(new Rect2D(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY), graph.getSpatialIndex().getBoundaries()); + } + + @Test + public void testGetBoundariesSingleNodeInView() { + GraphStore graphStore = GraphGenerator.generateEmptyGraphStore(getSpatialConfig()); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + // Add a node to the graph store + NodeImpl node1 = (NodeImpl) graphStore.factory.newNode("1"); + node1.setPosition(100, 200); + node1.setSize(10); + graphStore.addNode(node1); + + NodeImpl node2 = (NodeImpl) graphStore.factory.newNode("2"); + node2.setPosition(500, 600); + node2.setSize(20); + graphStore.addNode(node2); + + // Add only node1 to the view + view.addNode(node1); + + DirectedSubgraph graph = store.getDirectedGraph(view); + + // Should return boundaries only for node1 + Rect2D boundaries = graph.getSpatialIndex().getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, 90f); // 100 - 10 + Assert.assertEquals(boundaries.minY, 190f); // 200 - 10 + Assert.assertEquals(boundaries.maxX, 110f); // 100 + 10 + Assert.assertEquals(boundaries.maxY, 210f); // 200 + 10 + } + + @Test + public void testGetBoundariesMultipleNodesInView() { + GraphStore graphStore = GraphGenerator.generateEmptyGraphStore(getSpatialConfig()); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + // Add nodes to the graph store + NodeImpl node1 = (NodeImpl) graphStore.factory.newNode("1"); + node1.setPosition(0, 0); + node1.setSize(5); + graphStore.addNode(node1); + + NodeImpl node2 = (NodeImpl) graphStore.factory.newNode("2"); + node2.setPosition(100, 200); + node2.setSize(10); + graphStore.addNode(node2); + + NodeImpl node3 = (NodeImpl) graphStore.factory.newNode("3"); + node3.setPosition(500, 600); // This node won't be in the view + node3.setSize(20); + graphStore.addNode(node3); + + // Add only node1 and node2 to the view + view.addNode(node1); + view.addNode(node2); + + DirectedSubgraph graph = store.getDirectedGraph(view); + + // Should return boundaries only for node1 and node2 + Rect2D boundaries = graph.getSpatialIndex().getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, -5f); // node1: 0 - 5 + Assert.assertEquals(boundaries.minY, -5f); // node1: 0 - 5 + Assert.assertEquals(boundaries.maxX, 110f); // node2: 100 + 10 + Assert.assertEquals(boundaries.maxY, 210f); // node2: 200 + 10 + } + + @Test + public void testGetBoundariesViewSubsetVsFullGraph() { + GraphStore graphStore = GraphGenerator.generateEmptyGraphStore(getSpatialConfig()); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + // Add nodes to the graph store + NodeImpl node1 = (NodeImpl) graphStore.factory.newNode("1"); + node1.setPosition(0, 0); + node1.setSize(5); + graphStore.addNode(node1); + + NodeImpl node2 = (NodeImpl) graphStore.factory.newNode("2"); + node2.setPosition(100, 200); + node2.setSize(10); + graphStore.addNode(node2); + + NodeImpl node3 = (NodeImpl) graphStore.factory.newNode("3"); + node3.setPosition(-50, -100); + node3.setSize(15); + graphStore.addNode(node3); + + // Add only first two nodes to the view + view.addNode(node1); + view.addNode(node2); + + DirectedSubgraph viewGraph = store.getDirectedGraph(view); + DirectedSubgraph fullGraph = graphStore; + + // Get boundaries for both + Rect2D viewBoundaries = viewGraph.getSpatialIndex().getBoundaries(); + Rect2D fullBoundaries = graphStore.spatialIndex.getBoundaries(); + + // View boundaries should only include node1 and node2 + Assert.assertNotNull(viewBoundaries); + Assert.assertEquals(viewBoundaries.minX, -5f); // node1: 0 - 5 + Assert.assertEquals(viewBoundaries.minY, -5f); // node1: 0 - 5 + Assert.assertEquals(viewBoundaries.maxX, 110f); // node2: 100 + 10 + Assert.assertEquals(viewBoundaries.maxY, 210f); // node2: 200 + 10 + + // Full graph boundaries should include all nodes + Assert.assertNotNull(fullBoundaries); + Assert.assertEquals(fullBoundaries.minX, -65f); // node3: -50 - 15 + Assert.assertEquals(fullBoundaries.minY, -115f); // node3: -100 - 15 + Assert.assertEquals(fullBoundaries.maxX, 110f); // node2: 100 + 10 + Assert.assertEquals(fullBoundaries.maxY, 210f); // node2: 200 + 10 + + // They should be different + Assert.assertFalse(viewBoundaries.minX == fullBoundaries.minX); + Assert.assertFalse(viewBoundaries.minY == fullBoundaries.minY); + } + + @Test + public void testGetBoundariesAfterViewChanges() { + GraphStore graphStore = GraphGenerator.generateEmptyGraphStore(getSpatialConfig()); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + // Add nodes to the graph store + NodeImpl node1 = (NodeImpl) graphStore.factory.newNode("1"); + node1.setPosition(0, 0); + node1.setSize(5); + graphStore.addNode(node1); + + NodeImpl node2 = (NodeImpl) graphStore.factory.newNode("2"); + node2.setPosition(100, 200); + node2.setSize(10); + graphStore.addNode(node2); + + DirectedSubgraph graph = store.getDirectedGraph(view); + + // Initially empty view + Rect2D boundaries = graph.getSpatialIndex().getBoundaries(); + Rect2D expected = new Rect2D(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY); + Assert.assertEquals(expected, boundaries); + + // Add first node to view + view.addNode(node1); + Rect2D boundaries1 = graph.getSpatialIndex().getBoundaries(); + Assert.assertNotNull(boundaries1); + Assert.assertEquals(boundaries1.minX, -5f); + Assert.assertEquals(boundaries1.maxX, 5f); + + // Add second node to view + view.addNode(node2); + Rect2D boundaries2 = graph.getSpatialIndex().getBoundaries(); + Assert.assertNotNull(boundaries2); + Assert.assertEquals(boundaries2.minX, -5f); + Assert.assertEquals(boundaries2.maxX, 110f); + + // Remove first node from view + view.removeNode(node1); + Rect2D boundaries3 = graph.getSpatialIndex().getBoundaries(); + Assert.assertNotNull(boundaries3); + Assert.assertEquals(boundaries3.minX, 90f); // Only node2 remains + Assert.assertEquals(boundaries3.maxX, 110f); + + // Remove last node from view + view.removeNode(node2); + Assert.assertEquals(expected, graph.getSpatialIndex().getBoundaries()); + } + // UTILITY - private boolean isIterablesEqual(ElementIterable n1, ElementIterable n2) { - ObjectSet s1 = new ObjectOpenHashSet(); - for (Object n : n1) { - s1.add(((Element) n).getId()); - } - ObjectSet s2 = new ObjectOpenHashSet(); - for (Object n : n2) { - s2.add(((Element) n).getId()); - } - return s1.equals(s2); + private void assertIterablesEqual(NodeIterable n1, NodeIterable n2) { + Assert.assertEquals(n1.toCollection(), n2.toCollection()); + Assert.assertEquals(n1.stream().collect(Collectors.toList()), n2.stream().collect(Collectors.toList())); + Assert.assertEquals(n1.toArray(), n2.toArray()); + } + + private void assertIterablesEqual(EdgeIterable e1, EdgeIterable e2) { + Assert.assertEquals(e1.toCollection(), e2.toCollection()); + Assert.assertEquals(e1.stream().collect(Collectors.toList()), e2.stream().collect(Collectors.toList())); + Assert.assertEquals(e1.toArray(), e2.toArray()); } private GraphStore convertToStore(GraphViewImpl view) { @@ -892,4 +1116,9 @@ private void addSomeElements(GraphStore store, GraphViewImpl view) { } } } + + // Configuration with spatial index enabled + private Configuration getSpatialConfig() { + return Configuration.builder().enableSpatialIndex(true).build(); + } } diff --git a/src/test/java/org/gephi/graph/impl/GraphViewImplTest.java b/src/test/java/org/gephi/graph/impl/GraphViewImplTest.java new file mode 100644 index 00000000..9606645f --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/GraphViewImplTest.java @@ -0,0 +1,1463 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Spliterator; +import org.gephi.graph.api.DirectedSubgraph; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Graph; +import org.gephi.graph.api.GraphFactory; +import org.gephi.graph.api.GraphView; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.Subgraph; +import org.gephi.graph.api.UndirectedSubgraph; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class GraphViewImplTest { + + @Test + public void testFill() { + GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + DirectedSubgraph graph = store.getDirectedGraph(view); + UndirectedSubgraph unGraph = store.getUndirectedGraph(view); + view.fill(); + + Assert.assertEquals(view.getNodeCount(), graphStore.getNodeCount()); + Assert.assertEquals(view.getEdgeCount(), graphStore.getEdgeCount()); + for (Edge e : graphStore.getEdges()) { + Assert.assertTrue(graph.contains(e)); + } + for (Node n : graphStore.getNodes()) { + Assert.assertTrue(graph.contains(n)); + Assert.assertEquals(graph.getDegree(n), graphStore.getDegree(n)); + } + for (int i = 0; i < graphStore.edgeTypeStore.length; i++) { + Assert.assertEquals(graph.getEdgeCount(i), graphStore.getEdgeCount(i)); + } + for (Edge e : graphStore.undirectedDecorator.getEdges()) { + Assert.assertTrue(unGraph.contains(e)); + } + for (Node n : graphStore.undirectedDecorator.getNodes()) { + Assert.assertTrue(unGraph.contains(n)); + } + for (int i = 0; i < graphStore.edgeTypeStore.length; i++) { + Assert.assertEquals(unGraph.getEdgeCount(i), graphStore.undirectedDecorator.getEdgeCount(i)); + } + } + + @Test + public void testFillTypeCountsWithParallelEdges() { + // Regression test: fill() used longDictionary[i].size() (distinct source-target + // pairs) instead of typeSize[i] (actual edge count), producing wrong typeCounts + // when parallel edges of the same type exist. + GraphModelImpl model = new GraphModelImpl( + org.gephi.graph.api.Configuration.builder().enableParallelEdgesSameType(true).build()); + GraphStore graphStore = model.store; + + Node n1 = graphStore.factory.newNode("1"); + Node n2 = graphStore.factory.newNode("2"); + graphStore.addNode(n1); + graphStore.addNode(n2); + + // Two parallel edges of type 0 between the same pair + Edge e1 = graphStore.factory.newEdge("e1", n1, n2, 0, 1.0, true); + Edge e2 = graphStore.factory.newEdge("e2", n1, n2, 0, 1.0, true); + graphStore.addEdge(e1); + graphStore.addEdge(e2); + + GraphViewImpl view = graphStore.viewStore.createView(); + view.fill(); + + Assert.assertEquals(view.getEdgeCount(), 2); + Assert.assertEquals(view.getEdgeCount(0), 2); + } + + @Test + public void testMainView() { + GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); + GraphViewImpl view = new GraphViewStore(graphStore).createView(); + + Assert.assertFalse(view.isMainView()); + } + + @Test + public void testAddNodeMainView() { + GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + NodeImpl node = new NodeImpl("A"); + graphStore.addNode(node); + + Assert.assertTrue(view.nodeBitVector.size() >= node.storeId); + boolean a = view.addNode(node); + Assert.assertTrue(a); + Assert.assertTrue(view.containsNode(node)); + } + + @Test + public void testAddEdgeMainView() { + GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + NodeImpl source = new NodeImpl("A"); + NodeImpl target = new NodeImpl("B"); + graphStore.addNode(source); + graphStore.addNode(target); + view.addNode(source); + view.addNode(target); + + EdgeImpl edge = new EdgeImpl("S", source, target, 0, 1.0, true); + graphStore.addEdge(edge); + + Assert.assertTrue(view.edgeBitVector.size() >= edge.storeId); + boolean a = view.addEdge(edge); + Assert.assertTrue(a); + Assert.assertTrue(view.containsEdge(edge)); + } + + @Test + public void testEdgeViewNodeBehaviors() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(false, true); + Subgraph subgraph = store.getGraph(view); + Assert.assertEquals(subgraph.getNodeCount(), graphStore.getNodeCount()); + Assert.assertEquals(subgraph.getNodes().stream().count(), graphStore.getNodeCount()); + for (Node node : subgraph.getNodes()) { + Assert.assertSame(subgraph.getNode(node.getId()), node); + Assert.assertEquals(subgraph.getDegree(node), 0); + } + } + + @Test + public void testViewDeepEquals() { + GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + NodeImpl n1 = graphStore.getNode("0"); + view.addNode(n1); + + Assert.assertTrue(view.deepEquals(view)); + + GraphViewImpl view2 = store.createView(); + + NodeImpl n2 = graphStore.getNode("0"); + view2.addNode(n2); + + Assert.assertTrue(view.deepEquals(view2)); + } + + @Test + public void testViewDeepHashCode() { + GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + NodeImpl n1 = graphStore.getNode("0"); + view.addNode(n1); + + Assert.assertEquals(view.hashCode(), view.hashCode()); + + GraphViewImpl view2 = store.createView(); + + NodeImpl n2 = graphStore.getNode("0"); + view2.addNode(n2); + + Assert.assertEquals(view.deepHashCode(), view2.deepHashCode()); + } + + @Test + public void testViewIntersection() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + GraphViewImpl view2 = store.createView(); + + EdgeImpl e1 = graphStore.getEdge("0"); + EdgeImpl e2 = graphStore.getEdge("5"); + NodeImpl n1 = e1.getSource(); + NodeImpl n2 = e1.getTarget(); + NodeImpl n3 = e2.getSource(); + NodeImpl n4 = e2.getTarget(); + view.addNode(n1); + view2.addNode(n1); + view.addNode(n2); + view2.addNode(n2); + + view.addNode(n3); + view.addNode(n4); + + view.addEdge(e1); + view2.addEdge(e1); + view.addEdge(e2); + + view.intersection(view2); + + // Positive assertions - expected elements ARE present + Assert.assertTrue(view.containsNode(n1)); + Assert.assertTrue(view.containsNode(n2)); + Assert.assertTrue(view.containsEdge(e1)); + + // Negative assertions - elements not in intersection should be absent + Assert.assertFalse(view.containsNode(n3)); + Assert.assertFalse(view.containsNode(n4)); + Assert.assertFalse(view.containsEdge(e2)); + + // Exact count assertions + Assert.assertEquals(view.getNodeCount(), 2, "Should have exactly 2 nodes after intersection"); + Assert.assertEquals(view.getEdgeCount(), 1, "Should have exactly 1 edge after intersection"); + + // Verify no other elements from the graph are present + for (Node n : graphStore.getNodes()) { + if (n != n1 && n != n2) { + Assert.assertFalse(view + .containsNode((NodeImpl) n), "Node " + n.getId() + " should not be in view after intersection"); + } + } + for (Edge e : graphStore.getEdges()) { + if (e != e1) { + Assert.assertFalse(view + .containsEdge((EdgeImpl) e), "Edge " + e.getId() + " should not be in view after intersection"); + } + } + + Assert.assertTrue(view2.deepEquals(view)); + } + + @Test + public void testViewIntersectionEdgeView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(false, true); + GraphViewImpl view2 = store.createView(false, true); + + view.fill(); + view2.fill(); + + int totalEdges = graphStore.getEdgeCount(); + EdgeImpl e1 = graphStore.getEdge("0"); + EdgeImpl e2 = graphStore.getEdge("5"); + + view.removeEdge(e1); + view2.removeEdge(e2); + + view.intersection(view2); + + // Negative assertions - removed edges should be absent + Assert.assertFalse(view.containsEdge(e1)); + Assert.assertFalse(view.containsEdge(e2)); + + // Exact count assertion - intersection excludes both removed edges + Assert.assertEquals(view + .getEdgeCount(), totalEdges - 2, "Should have all edges except e1 and e2 after intersection"); + + // Verify all other edges are present + for (Edge e : graphStore.getEdges()) { + if (e != e1 && e != e2) { + Assert.assertTrue(view + .containsEdge((EdgeImpl) e), "Edge " + e.getId() + " should be in view after intersection"); + } + } + } + + @Test + public void testViewIntersectionNodeView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(true, false); + GraphViewImpl view2 = store.createView(); + + view.fill(); + view2.fill(); + + int totalNodes = graphStore.getNodeCount(); + EdgeImpl e1 = graphStore.getEdge("0"); + EdgeImpl e2 = graphStore.getEdge("5"); + NodeImpl s1 = e1.getSource(); + + view2.removeNode(s1); + view2.removeEdge(e2); + + view.intersection(view2); + + // Node intersection: s1 was removed from view2, so it should be absent + Assert.assertFalse(view.containsNode(s1)); + Assert.assertEquals(view.getNodeCount(), totalNodes - 1, "Should have all nodes except s1 after intersection"); + + // Edge intersection: e1 removed because s1 is gone (node view), e2 present + Assert.assertFalse(view.containsEdge(e1), "e1 should be absent (source node removed)"); + Assert.assertTrue(view.containsEdge(e2), "e2 should be present"); + + // Verify all other nodes are present + for (Node n : graphStore.getNodes()) { + if (n != s1) { + Assert.assertTrue(view + .containsNode((NodeImpl) n), "Node " + n.getId() + " should be in view after intersection"); + } + } + } + + @Test + public void testViewUnion() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + GraphViewImpl view2 = store.createView(); + + EdgeImpl e1 = graphStore.getEdge("0"); + EdgeImpl e2 = graphStore.getEdge("5"); + NodeImpl n1 = e1.getSource(); + NodeImpl n2 = e1.getTarget(); + NodeImpl n3 = e2.getSource(); + NodeImpl n4 = e2.getTarget(); + view.addNode(n1); + view.addNode(n2); + + view2.addNode(n3); + view2.addNode(n4); + + view.addEdge(e1); + view2.addEdge(e2); + + view.union(view2); + + // Positive assertions - expected elements ARE present + Assert.assertTrue(view.containsNode(n1)); + Assert.assertTrue(view.containsNode(n2)); + Assert.assertTrue(view.containsEdge(e1)); + Assert.assertTrue(view.containsNode(n3)); + Assert.assertTrue(view.containsNode(n4)); + Assert.assertTrue(view.containsEdge(e2)); + + // Exact count assertions - verify ONLY expected elements + Assert.assertEquals(view.getNodeCount(), 4, "Should have exactly 4 nodes after union"); + Assert.assertEquals(view.getEdgeCount(), 2, "Should have exactly 2 edges after union"); + + // Negative assertions - verify other graph elements are NOT present + for (Node n : graphStore.getNodes()) { + if (n != n1 && n != n2 && n != n3 && n != n4) { + Assert.assertFalse(view + .containsNode((NodeImpl) n), "Node " + n.getId() + " should not be in view after union"); + } + } + for (Edge e : graphStore.getEdges()) { + if (e != e1 && e != e2) { + Assert.assertFalse(view + .containsEdge((EdgeImpl) e), "Edge " + e.getId() + " should not be in view after union"); + } + } + } + + @Test + public void testViewUnionEdgeView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(false, true); + GraphViewImpl view2 = store.createView(false, true); + + EdgeImpl e1 = graphStore.getEdge("0"); + EdgeImpl e2 = graphStore.getEdge("5"); + + view.addEdge(e1); + view2.addEdge(e2); + + view.union(view2); + + // Positive assertions + Assert.assertTrue(view.containsEdge(e1)); + Assert.assertTrue(view.containsEdge(e2)); + + // Exact count assertion + Assert.assertEquals(view.getEdgeCount(), 2, "Should have exactly 2 edges after union"); + + // Negative assertions - verify other edges are NOT present + for (Edge e : graphStore.getEdges()) { + if (e != e1 && e != e2) { + Assert.assertFalse(view + .containsEdge((EdgeImpl) e), "Edge " + e.getId() + " should not be in view after union"); + } + } + } + + @Test + public void testViewUnionNodeView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(true, false); + GraphViewImpl view2 = store.createView(true, true); + + EdgeImpl e1 = graphStore.getEdge("0"); + EdgeImpl e2 = graphStore.getEdge("5"); + NodeImpl n1 = e1.getSource(); + NodeImpl n2 = e1.getTarget(); + NodeImpl n3 = e2.getSource(); + NodeImpl n4 = e2.getTarget(); + + view2.addAllNodes(Arrays.asList(new NodeImpl[] { n1, n2, n3, n4 })); + view2.addEdge(e1); + Assert.assertFalse(view.containsEdge(e2)); + + view.union(view2); + + // Positive assertions + Assert.assertTrue(view.containsEdge(e1)); + Assert.assertTrue(view.containsEdge(e2), "e2 should be present (both endpoints are in union)"); + + // All 4 nodes should be present + Assert.assertTrue(view.containsNode(n1)); + Assert.assertTrue(view.containsNode(n2)); + Assert.assertTrue(view.containsNode(n3)); + Assert.assertTrue(view.containsNode(n4)); + Assert.assertEquals(view.getNodeCount(), 4, "Should have exactly 4 nodes after union"); + + // Verify no other nodes are present + for (Node n : graphStore.getNodes()) { + if (n != n1 && n != n2 && n != n3 && n != n4) { + Assert.assertFalse(view + .containsNode((NodeImpl) n), "Node " + n.getId() + " should not be in view after union"); + } + } + } + + @Test + public void testViewNot() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + view.not(); + + for (Node n : graphStore.getNodes()) { + Assert.assertTrue(view.containsNode((NodeImpl) n)); + } + for (Edge e : graphStore.getEdges()) { + Assert.assertTrue(view.containsEdge((EdgeImpl) e)); + } + Assert.assertEquals(view.getNodeCount(), graphStore.getNodeCount()); + Assert.assertEquals(view.getEdgeCount(), graphStore.getEdgeCount()); + + view.not(); + + Assert.assertEquals(view.getNodeCount(), 0); + Assert.assertEquals(view.getEdgeCount(), 0); + + EdgeImpl e1 = graphStore.getEdge("0"); + NodeImpl n1 = e1.getSource(); + NodeImpl n2 = e1.getTarget(); + + view.addNode(n1); + view.addNode(n2); + view.addEdge(e1); + + view.not(); + + Assert.assertFalse(view.containsNode(n1)); + Assert.assertFalse(view.containsNode(n2)); + Assert.assertFalse(view.containsEdge(e1)); + } + + @Test + public void testViewNotInterEdges() { + GraphStore graphStore = new GraphModelImpl().store; + GraphFactory factory = graphStore.factory; + Node n1 = factory.newNode(); + Node n2 = factory.newNode(); + Node n3 = factory.newNode(); + graphStore.addAllNodes(Arrays.asList(new Node[] { n1, n2, n3 })); + Edge e1 = factory.newEdge(n1, n2, false); + Edge e2 = factory.newEdge(n1, n3, false); + graphStore.addAllEdges(Arrays.asList(new Edge[] { e1, e2 })); + + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + view.fill(); + Graph viewGraph = store.getGraph(view); + viewGraph.removeNode(n3); + + view.not(); + + Assert.assertEquals(viewGraph.getNodeCount(), 1); + Assert.assertEquals(viewGraph.getEdgeCount(), 0); + } + + @Test + public void testViewNotNodeView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(true, false); + + EdgeImpl e1 = graphStore.getEdge("0"); + NodeImpl n1 = e1.getSource(); + NodeImpl n2 = e1.getTarget(); + + view.addNode(n1); + view.addNode(n2); + + view.not(); + + Assert.assertFalse(view.containsNode(n1)); + Assert.assertFalse(view.containsNode(n1)); + Assert.assertFalse(view.containsEdge(e1)); + + view.not(); + + Assert.assertTrue(view.containsNode(n1)); + Assert.assertTrue(view.containsNode(n2)); + Assert.assertTrue(view.containsEdge(e1)); + } + + @Test + public void testNodeView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(true, false); + + for (Node n : graphStore.getNodes()) { + view.addNode(n); + + Assert.assertTrue(view.containsNode((NodeImpl) n)); + for (Edge e : graphStore.getEdges(n)) { + Node opposite = graphStore.getOpposite(n, e); + if (view.containsNode((NodeImpl) opposite)) { + Assert.assertTrue(view.containsEdge((EdgeImpl) e)); + } + } + } + } + + @Test + public void testNodeViewSpliteratorParallelCollectAcrossBlocks() { + // Regression: NodeViewSpliterator used to keep SIZED after splitting, with a per-half + // totalSize derived from a proportional estimate. Parallel collect would then fail + // with "Accept exceeded fixed size of N" inside FixedNodeBuilder. The view here only + // contains the first block's nodes, so the proportional estimate undercounts. + GraphStore graphStore = new GraphModelImpl().store; + int totalNodes = GraphStoreConfiguration.NODESTORE_BLOCK_SIZE * 2 + 256; + NodeImpl[] nodes = GraphGenerator.generateNodeList(totalNodes, graphStore); + graphStore.addAllNodes(Arrays.asList(nodes)); + + GraphViewStore viewStore = graphStore.viewStore; + GraphViewImpl view = viewStore.createView(true, false); + int inViewCount = GraphStoreConfiguration.NODESTORE_BLOCK_SIZE; + for (int i = 0; i < inViewCount; i++) { + view.addNode(nodes[i]); + } + Assert.assertEquals(view.getNodeCount(), inViewCount); + Assert.assertTrue(graphStore.nodeStore.blocksCount > 1, "Need multiple blocks to exercise trySplit"); + + Subgraph subgraph = viewStore.getGraph(view); + Collection collected = subgraph.getNodes().toCollection(); + Assert.assertEquals(collected.size(), inViewCount); + Assert.assertEquals(new HashSet<>(collected).size(), inViewCount); + } + + @Test + public void testNodeViewSpliteratorSplitDropsSizedCharacteristic() { + GraphStore graphStore = new GraphModelImpl().store; + int totalNodes = GraphStoreConfiguration.NODESTORE_BLOCK_SIZE + 128; + NodeImpl[] nodes = GraphGenerator.generateNodeList(totalNodes, graphStore); + graphStore.addAllNodes(Arrays.asList(nodes)); + + GraphViewStore viewStore = graphStore.viewStore; + GraphViewImpl view = viewStore.createView(true, false); + for (int i = 0; i < totalNodes / 2; i++) { + view.addNode(nodes[i]); + } + + Subgraph subgraph = viewStore.getGraph(view); + Spliterator root = subgraph.getNodes().spliterator(); + Assert.assertTrue((root.characteristics() & Spliterator.SIZED) != 0); + + Spliterator left = root.trySplit(); + Assert.assertNotNull(left); + Assert.assertTrue((root.characteristics() & Spliterator.SIZED) == 0); + Assert.assertTrue((left.characteristics() & Spliterator.SIZED) == 0); + } + + @Test + public void testNodeViewEdgeUpdate() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(true, false); + + NodeImpl n1 = graphStore.getNode("0"); + NodeImpl n2 = graphStore.getNode("1"); + + view.addNode(n1); + view.addNode(n2); + + Assert.assertNull(graphStore.getEdge(n1, n2)); + EdgeImpl edge = (EdgeImpl) graphStore.factory.newEdge("edge", n1, n2, 0, 1.0, true); + graphStore.addEdge(edge); + + Assert.assertTrue(view.containsEdge(edge)); + } + + @Test + public void testEdgeViewUpdate() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(false, true); + + GraphFactory factory = graphStore.factory; + Node n1 = factory.newNode("foo1"); + Node n2 = factory.newNode("foo2"); + graphStore.addNode(n1); + graphStore.addNode(n2); + Assert.assertEquals(view.getNodeCount(), graphStore.getNodeCount()); + Edge e1 = factory.newEdge("foo", n1, n2, 0, 0.0, true); + graphStore.addEdge(e1); + Assert.assertFalse(view.containsEdge(e1)); + } + + @Test + public void testIsNodeView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + + GraphView v1 = store.createView(); + GraphView v2 = store.createView(true, false); + + Assert.assertTrue(v1.isNodeView() && v1.isEdgeView()); + Assert.assertTrue(v2.isNodeView() && !v2.isEdgeView()); + } + + @Test + public void testIsEdgeView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + + GraphView v1 = store.createView(); + GraphView v2 = store.createView(false, true); + + Assert.assertTrue(v1.isNodeView() && v1.isEdgeView()); + Assert.assertTrue(!v2.isNodeView() && v2.isEdgeView()); + } + + @Test + public void testDefaultVisibleView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphView view = graphStore.viewStore.getVisibleView(); + + Assert.assertNotNull(view); + Assert.assertEquals(view, graphStore.mainGraphView); + } + + @Test + public void testMutualCounts() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStoreWithMutualEdge(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(false, true); + view.fill(); + Assert.assertEquals(view.getUndirectedEdgeCount(), 1); + Assert.assertEquals(view.getEdgeCount(), 2); + view.removeEdge(graphStore.getEdge("1")); + Assert.assertEquals(view.getUndirectedEdgeCount(), 1); + } + + @Test + public void testEdgeViewSetEdgeType() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(false, true); + + EdgeImpl e0 = graphStore.getEdge("0"); + Assert.assertEquals(view.getEdgeCount(0), 0); + view.addEdge(e0); + Assert.assertEquals(view.getEdgeCount(0), 1); + e0.setType(1); + Assert.assertEquals(view.getEdgeCount(0), 0); + Assert.assertEquals(view.getEdgeCount(1), 1); + } + + @Test + public void testEdgeViewSetEdgeTypeMutualEdges() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStoreWithMutualEdge(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(true, false); + view.fill(); + + EdgeImpl e0 = graphStore.getEdge("0"); + e0.setType(1); + Assert.assertEquals(view.getEdgeCount(0), 1); + Assert.assertEquals(view.getEdgeCount(1), 1); + Assert.assertEquals(view.getUndirectedEdgeCount(0), 1); + Assert.assertEquals(view.getUndirectedEdgeCount(1), 1); + + EdgeImpl e1 = graphStore.getEdge("1"); + e1.setType(1); + Assert.assertEquals(view.getEdgeCount(0), 0); + Assert.assertEquals(view.getEdgeCount(1), 2); + Assert.assertEquals(view.getUndirectedEdgeCount(0), 0); + Assert.assertEquals(view.getUndirectedEdgeCount(1), 1); + } + + @Test + public void testCopyConstructor() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStoreWithMutualEdge(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + view.fill(); + + // Original view should have mutual edges counted + int originalEdgeCount = view.getEdgeCount(); + int originalMutualCount = view.mutualEdgesCount; + int originalUndirectedCount = view.getUndirectedEdgeCount(); + + // Create a copy using the copy constructor + GraphViewImpl copiedView = new GraphViewImpl(view, true, true); + + // Verify + Assert.assertEquals(copiedView.getNodeCount(), view + .getNodeCount(), "Node count should be the same in copied view"); + Assert.assertEquals(copiedView.nodeBitVector, view.nodeBitVector, "Node bit vector should be the same in copied view"); + Assert.assertEquals(copiedView.edgeBitVector, view.edgeBitVector, "Edge bit vector should be the same in copied view"); + + // Verify that mutualEdgesCount was copied correctly + Assert.assertEquals(copiedView.mutualEdgesCount, originalMutualCount, "mutualEdgesCount should be copied in copy constructor"); + Assert.assertEquals(copiedView.getEdgeCount(), originalEdgeCount); + Assert.assertEquals(copiedView + .getUndirectedEdgeCount(), originalUndirectedCount, "getUndirectedEdgeCount() should return correct value after copy"); + } + + @Test + public void testFilledViewRequiresExplicitAdd() { + // Test that users must explicitly add edges to filled views + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + view.fill(); + int initialEdgeCount = view.getEdgeCount(); + + // Add a new edge to the main graph store + NodeImpl n1 = graphStore.getNode("0"); + NodeImpl n2 = graphStore.getNode("1"); + EdgeImpl newEdge = new EdgeImpl("newEdge", n1, n2, 0, 1.0, true); + graphStore.addEdge(newEdge); + + // Edge should not be in view yet + Assert.assertFalse(view.containsEdge(newEdge)); + + // Explicitly add the edge to the view + boolean added = view.addEdge(newEdge); + + // Now it should be in the view + Assert.assertTrue(added, "addEdge should return true"); + Assert.assertTrue(view.containsEdge(newEdge), "Edge should be in view after explicit add"); + Assert.assertEquals(view.getEdgeCount(), initialEdgeCount + 1); + } + + // ========== Tests for Mutual Edge Counts in Bulk Operations ========== + + @Test + public void testIntersectionWithMutualEdges() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStoreWithMutualEdge(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view1 = store.createView(); + GraphViewImpl view2 = store.createView(); + + // Fill both views + view1.fill(); + view2.fill(); + + // Initial state: both views have mutual edges (mutual count is 1 for a pair) + Assert.assertEquals(view1.mutualEdgesCount, 1, "View1 should have mutual count of 1"); + Assert.assertEquals(view1.getEdgeCount(), 2, "View1 should have 2 edges"); + Assert.assertEquals(view1.getUndirectedEdgeCount(), 1, "View1 should have 1 undirected edge"); + + // Remove one of the mutual edges from view2 + EdgeImpl e0 = graphStore.getEdge("0"); + EdgeImpl e1 = graphStore.getEdge("1"); + view2.removeEdge(e1); + + // After removing one mutual edge, view2 should have no mutual edges + Assert.assertEquals(view2.mutualEdgesCount, 0, "View2 should have 0 mutual edges after removal"); + Assert.assertEquals(view2.getEdgeCount(), 1, "View2 should have 1 edge"); + Assert.assertEquals(view2.getUndirectedEdgeCount(), 1, "View2 should have 1 undirected edge"); + + // Intersection should result in view1 losing its mutual edge status + view1.intersection(view2); + + Assert.assertEquals(view1.mutualEdgesCount, 0, "View1 should have 0 mutual edges after intersection"); + Assert.assertEquals(view1.getEdgeCount(), 1, "View1 should have 1 edge total"); + Assert.assertEquals(view1.getUndirectedEdgeCount(), 1, "View1 should have 1 undirected edge"); + + // Verify which edge remains + Assert.assertTrue(view1.containsEdge(e0), "e0 should remain after intersection"); + Assert.assertFalse(view1.containsEdge(e1), "e1 should be absent after intersection"); + } + + @Test + public void testUnionWithMutualEdges() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStoreWithMutualEdge(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view1 = store.createView(); + GraphViewImpl view2 = store.createView(); + + EdgeImpl e0 = graphStore.getEdge("0"); + EdgeImpl e1 = graphStore.getEdge("1"); + NodeImpl n1 = e0.getSource(); + NodeImpl n2 = e0.getTarget(); + + // View1 has only one edge of the mutual pair + view1.addNode(n1); + view1.addNode(n2); + view1.addEdge(e0); + + // View2 has only the other edge of the mutual pair + view2.addNode(n1); + view2.addNode(n2); + view2.addEdge(e1); + + // Neither view should have mutual edges yet + Assert.assertEquals(view1.mutualEdgesCount, 0, "View1 should have 0 mutual edges initially"); + Assert.assertEquals(view2.mutualEdgesCount, 0, "View2 should have 0 mutual edges initially"); + + // Union should create mutual edges (mutual count is 1 for a pair) + view1.union(view2); + + Assert.assertEquals(view1.mutualEdgesCount, 1, "View1 should have mutual count of 1 after union"); + Assert.assertEquals(view1.getEdgeCount(), 2, "View1 should have 2 edges total"); + Assert.assertEquals(view1.getUndirectedEdgeCount(), 1, "View1 should have 1 undirected edge"); + + // Verify both edges are present + Assert.assertTrue(view1.containsEdge(e0), "e0 should be present after union"); + Assert.assertTrue(view1.containsEdge(e1), "e1 should be present after union"); + + // Verify only the expected nodes are present + Assert.assertEquals(view1.getNodeCount(), 2, "Should have exactly 2 nodes"); + Assert.assertTrue(view1.containsNode(n1)); + Assert.assertTrue(view1.containsNode(n2)); + } + + @Test + public void testNotWithMutualEdges() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStoreWithMutualEdge(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + EdgeImpl e0 = graphStore.getEdge("0"); + NodeImpl n1 = e0.getSource(); + NodeImpl n2 = e0.getTarget(); + + // Add only one edge of the mutual pair + view.addNode(n1); + view.addNode(n2); + view.addEdge(e0); + + Assert.assertEquals(view.mutualEdgesCount, 0, "View should have 0 mutual edges initially"); + Assert.assertEquals(view.getEdgeCount(), 1, "View should have 1 edge"); + Assert.assertEquals(view.getNodeCount(), 2, "View should have 2 nodes"); + + // NOT operation flips both nodes and edges + // Since there are only 2 nodes total in the graph, after NOT we have 0 nodes + // Edges without endpoints get removed, so we end up with 0 edges + view.not(); + + Assert.assertEquals(view.getNodeCount(), 0, "View should have 0 nodes after NOT (graph has 2 nodes total)"); + Assert.assertEquals(view.getEdgeCount(), 0, "View should have 0 edges after NOT (no nodes, so no edges)"); + } + + // ========== Tests for Multi-Type Edge Counts in Bulk Operations ========== + + @Test + public void testIntersectionMultipleEdgeTypes() { + GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view1 = store.createView(); + GraphViewImpl view2 = store.createView(); + + // Fill both views + view1.fill(); + view2.fill(); + + // Verify initial state has multiple edge types + int type0CountInitial = view1.getEdgeCount(0); + int type1CountInitial = view1.getEdgeCount(1); + int type2CountInitial = view1.getEdgeCount(2); + Assert.assertTrue(type0CountInitial > 0, "Should have type 0 edges"); + Assert.assertTrue(type1CountInitial > 0, "Should have type 1 edges"); + + // Remove all type 0 edges from view2 + for (Edge e : graphStore.getEdges().toArray()) { + if (e.getType() == 0) { + view2.removeEdge(e); + } + } + + Assert.assertEquals(view2.getEdgeCount(0), 0, "View2 should have 0 type 0 edges"); + Assert.assertEquals(view2.getEdgeCount(1), type1CountInitial, "View2 should still have all type 1 edges"); + + // Intersection should remove all type 0 edges from view1 + view1.intersection(view2); + + Assert.assertEquals(view1.getEdgeCount(0), 0, "View1 should have 0 type 0 edges after intersection"); + Assert.assertEquals(view1 + .getEdgeCount(1), type1CountInitial, "View1 should have all type 1 edges after intersection"); + Assert.assertEquals(view1 + .getEdgeCount(2), type2CountInitial, "View1 should have all type 2 edges after intersection"); + Assert.assertEquals(view1 + .getEdgeCount(), type1CountInitial + type2CountInitial, "Total edge count should match sum of type 1 and type 2"); + + // Verify no type 0 edges are present + for (Edge e : graphStore.getEdges()) { + if (e.getType() == 0) { + Assert.assertFalse(view1.containsEdge((EdgeImpl) e), "Type 0 edge " + e + .getId() + " should not be in view after intersection"); + } + } + } + + @Test + public void testUnionMultipleEdgeTypes() { + GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view1 = store.createView(); + GraphViewImpl view2 = store.createView(); + + // Add only type 0 edges to view1 + for (Node n : graphStore.getNodes()) { + view1.addNode(n); + } + for (Edge e : graphStore.getEdges().toArray()) { + if (e.getType() == 0) { + view1.addEdge(e); + } + } + + // Add only type 1 and type 2 edges to view2 + for (Node n : graphStore.getNodes()) { + view2.addNode(n); + } + for (Edge e : graphStore.getEdges().toArray()) { + if (e.getType() == 1 || e.getType() == 2) { + view2.addEdge(e); + } + } + + int type0Count = view1.getEdgeCount(0); + int type1Count = view2.getEdgeCount(1); + int type2Count = view2.getEdgeCount(2); + + Assert.assertTrue(type0Count > 0, "View1 should have type 0 edges"); + Assert.assertEquals(view2.getEdgeCount(0), 0, "View2 should have no type 0 edges"); + Assert.assertTrue(type1Count > 0, "View2 should have type 1 edges"); + Assert.assertTrue(type2Count > 0, "View2 should have type 2 edges"); + + // Union should combine both types + view1.union(view2); + + Assert.assertEquals(view1.getEdgeCount(0), type0Count, "View1 should have all type 0 edges after union"); + Assert.assertEquals(view1.getEdgeCount(1), type1Count, "View1 should have all type 1 edges after union"); + Assert.assertEquals(view1.getEdgeCount(2), type2Count, "View1 should have all type 2 edges after union"); + Assert.assertEquals(view1 + .getEdgeCount(), type0Count + type1Count + type2Count, "Total should be sum of all types"); + + // Verify all edges of each type are present + for (Edge e : graphStore.getEdges()) { + Assert.assertTrue(view1.containsEdge((EdgeImpl) e), "Edge " + e.getId() + " of type " + e + .getType() + " should be in view after union"); + } + } + + @Test + public void testNotMultipleEdgeTypes() { + GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + // Add all nodes but only type 0 edges + for (Node n : graphStore.getNodes()) { + view.addNode(n); + } + for (Edge e : graphStore.getEdges().toArray()) { + if (e.getType() == 0) { + view.addEdge(e); + } + } + + int type0Count = view.getEdgeCount(0); + int nodeCount = view.getNodeCount(); + int totalNodesInStore = graphStore.getNodeCount(); + + Assert.assertTrue(type0Count > 0, "View should have type 0 edges"); + + // NOT operation flips both nodes and edges + // Since we have all nodes, after NOT we have 0 nodes + // All edges get removed because they have no valid endpoints + view.not(); + + Assert.assertEquals(view + .getNodeCount(), totalNodesInStore - nodeCount, "View should have inverted node count after NOT"); + Assert.assertEquals(view + .getEdgeCount(), 0, "View should have 0 edges after NOT (no nodes means no valid edges)"); + } + + // ========== Tests for Empty View Edge Cases ========== + + @Test + public void testIntersectionWithEmptyView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view1 = store.createView(); + GraphViewImpl view2 = store.createView(); // Empty view + + // Fill view1 + view1.fill(); + int initialNodeCount = view1.getNodeCount(); + int initialEdgeCount = view1.getEdgeCount(); + + Assert.assertTrue(initialNodeCount > 0, "View1 should have nodes"); + Assert.assertTrue(initialEdgeCount > 0, "View1 should have edges"); + Assert.assertEquals(view2.getNodeCount(), 0, "View2 should be empty"); + Assert.assertEquals(view2.getEdgeCount(), 0, "View2 should be empty"); + + // Intersection with empty view should result in empty view1 + view1.intersection(view2); + + Assert.assertEquals(view1.getNodeCount(), 0, "View1 should be empty after intersection with empty view"); + Assert.assertEquals(view1.getEdgeCount(), 0, "View1 should have no edges after intersection with empty view"); + + // Verify all elements are absent + for (Node n : graphStore.getNodes()) { + Assert.assertFalse(view1.containsNode((NodeImpl) n), "Node " + n + .getId() + " should not be in view after intersection with empty view"); + } + for (Edge e : graphStore.getEdges()) { + Assert.assertFalse(view1.containsEdge((EdgeImpl) e), "Edge " + e + .getId() + " should not be in view after intersection with empty view"); + } + } + + @Test + public void testIntersectionOfEmptyView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view1 = store.createView(); // Empty view + GraphViewImpl view2 = store.createView(); + + // Fill view2 + view2.fill(); + + Assert.assertEquals(view1.getNodeCount(), 0, "View1 should be empty"); + Assert.assertTrue(view2.getNodeCount() > 0, "View2 should have nodes"); + + // Intersection of empty view with filled view should stay empty + view1.intersection(view2); + + Assert.assertEquals(view1.getNodeCount(), 0, "View1 should still be empty after intersection"); + Assert.assertEquals(view1.getEdgeCount(), 0, "View1 should still have no edges after intersection"); + + // Verify all elements are absent + for (Node n : graphStore.getNodes()) { + Assert.assertFalse(view1.containsNode((NodeImpl) n), "Node " + n + .getId() + " should not be in empty view after intersection"); + } + } + + @Test + public void testUnionWithEmptyView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view1 = store.createView(); + GraphViewImpl view2 = store.createView(); // Empty view + + // Fill view1 + view1.fill(); + int initialNodeCount = view1.getNodeCount(); + int initialEdgeCount = view1.getEdgeCount(); + + Assert.assertTrue(initialNodeCount > 0, "View1 should have nodes"); + Assert.assertTrue(initialEdgeCount > 0, "View1 should have edges"); + Assert.assertEquals(view2.getNodeCount(), 0, "View2 should be empty"); + + // Union with empty view should not change view1 + view1.union(view2); + + Assert.assertEquals(view1.getNodeCount(), initialNodeCount, "View1 node count should not change"); + Assert.assertEquals(view1.getEdgeCount(), initialEdgeCount, "View1 edge count should not change"); + + // Verify all elements are still present + for (Node n : graphStore.getNodes()) { + Assert.assertTrue(view1.containsNode((NodeImpl) n), "Node " + n + .getId() + " should still be in view after union with empty view"); + } + for (Edge e : graphStore.getEdges()) { + Assert.assertTrue(view1.containsEdge((EdgeImpl) e), "Edge " + e + .getId() + " should still be in view after union with empty view"); + } + } + + @Test + public void testUnionOfEmptyView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view1 = store.createView(); // Empty view + GraphViewImpl view2 = store.createView(); + + // Fill view2 + view2.fill(); + int view2NodeCount = view2.getNodeCount(); + int view2EdgeCount = view2.getEdgeCount(); + + Assert.assertEquals(view1.getNodeCount(), 0, "View1 should be empty"); + Assert.assertTrue(view2NodeCount > 0, "View2 should have nodes"); + + // Union of empty view with filled view should fill view1 + view1.union(view2); + + Assert.assertEquals(view1.getNodeCount(), view2NodeCount, "View1 should have same node count as view2"); + Assert.assertEquals(view1.getEdgeCount(), view2EdgeCount, "View1 should have same edge count as view2"); + + // Verify all elements are present + for (Node n : graphStore.getNodes()) { + Assert.assertTrue(view1.containsNode((NodeImpl) n), "Node " + n.getId() + " should be in view after union"); + } + for (Edge e : graphStore.getEdges()) { + Assert.assertTrue(view1.containsEdge((EdgeImpl) e), "Edge " + e.getId() + " should be in view after union"); + } + } + + @Test + public void testNotOnEmptyView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); // Empty view + + int totalNodes = graphStore.getNodeCount(); + int totalEdges = graphStore.getEdgeCount(); + + Assert.assertEquals(view.getNodeCount(), 0, "View should be empty initially"); + Assert.assertEquals(view.getEdgeCount(), 0, "View should have no edges initially"); + + // NOT on empty view should fill it completely + view.not(); + + Assert.assertEquals(view.getNodeCount(), totalNodes, "View should have all nodes after NOT"); + Assert.assertEquals(view.getEdgeCount(), totalEdges, "View should have all edges after NOT"); + + // Verify all elements are present + for (Node n : graphStore.getNodes()) { + Assert.assertTrue(view.containsNode((NodeImpl) n), "View should contain all nodes after NOT"); + } + for (Edge e : graphStore.getEdges()) { + Assert.assertTrue(view.containsEdge((EdgeImpl) e), "View should contain all edges after NOT"); + } + } + + @Test + public void testIntersectionBothEmpty() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view1 = store.createView(); // Empty + GraphViewImpl view2 = store.createView(); // Empty + + // Both views empty + Assert.assertEquals(view1.getNodeCount(), 0, "View1 should be empty"); + Assert.assertEquals(view2.getNodeCount(), 0, "View2 should be empty"); + + // Intersection of two empty views should stay empty + view1.intersection(view2); + + Assert.assertEquals(view1.getNodeCount(), 0, "View1 should still be empty"); + Assert.assertEquals(view1.getEdgeCount(), 0, "View1 should still have no edges"); + + // Verify all elements are absent (trivial case but validates correctness) + for (Node n : graphStore.getNodes()) { + Assert.assertFalse(view1 + .containsNode((NodeImpl) n), "No nodes should be in view after intersection of empty views"); + } + } + + @Test + public void testUnionBothEmpty() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view1 = store.createView(); // Empty + GraphViewImpl view2 = store.createView(); // Empty + + // Both views empty + Assert.assertEquals(view1.getNodeCount(), 0, "View1 should be empty"); + Assert.assertEquals(view2.getNodeCount(), 0, "View2 should be empty"); + + // Union of two empty views should stay empty + view1.union(view2); + + Assert.assertEquals(view1.getNodeCount(), 0, "View1 should still be empty"); + Assert.assertEquals(view1.getEdgeCount(), 0, "View1 should still have no edges"); + + // Verify all elements are absent (trivial case but validates correctness) + for (Node n : graphStore.getNodes()) { + Assert.assertFalse(view1 + .containsNode((NodeImpl) n), "No nodes should be in view after union of empty views"); + } + } + + // ========== Tests for Retain Operations ========== + + @Test + public void testRetainNodesBasic() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + view.fill(); + int initialNodeCount = view.getNodeCount(); + int initialEdgeCount = view.getEdgeCount(); + + // Retain all nodes - should return false (no change) + boolean changed = view.retainNodes(graphStore.getNodes().toCollection()); + Assert.assertFalse(changed, "Retaining all nodes should return false"); + Assert.assertEquals(view.getNodeCount(), initialNodeCount, "Node count should not change"); + Assert.assertEquals(view.getEdgeCount(), initialEdgeCount, "Edge count should not change"); + + // Retain subset of nodes + NodeImpl n1 = graphStore.getNode("0"); + NodeImpl n2 = graphStore.getNode("1"); + changed = view.retainNodes(Arrays.asList(n1, n2)); + + Assert.assertTrue(changed, "Retaining subset should return true"); + Assert.assertEquals(view.getNodeCount(), 2, "Should have exactly 2 nodes"); + Assert.assertTrue(view.containsNode(n1), "Should contain node 0"); + Assert.assertTrue(view.containsNode(n2), "Should contain node 1"); + } + + @Test + public void testRetainNodesEmpty() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + view.fill(); + + // Retain empty collection - should clear everything + boolean changed = view.retainNodes(Collections.emptyList()); + + Assert.assertTrue(changed, "Retaining empty list should return true"); + Assert.assertEquals(view.getNodeCount(), 0, "Should have no nodes"); + Assert.assertEquals(view.getEdgeCount(), 0, "Should have no edges"); + } + + @Test + public void testRetainEdgesBasic() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(false, true); // Edge view only + + view.fill(); + int initialEdgeCount = view.getEdgeCount(); + + // Retain all edges - should return false (no change) + boolean changed = view.retainEdges(graphStore.getEdges().toCollection()); + Assert.assertFalse(changed, "Retaining all edges should return false"); + Assert.assertEquals(view.getEdgeCount(), initialEdgeCount, "Edge count should not change"); + + // Retain subset of edges + EdgeImpl e1 = graphStore.getEdge("0"); + EdgeImpl e2 = graphStore.getEdge("1"); + changed = view.retainEdges(Arrays.asList(e1, e2)); + + Assert.assertTrue(changed, "Retaining subset should return true"); + Assert.assertEquals(view.getEdgeCount(), 2, "Should have exactly 2 edges"); + Assert.assertTrue(view.containsEdge(e1), "Should contain edge 0"); + Assert.assertTrue(view.containsEdge(e2), "Should contain edge 1"); + } + + @Test + public void testRetainEdgesEmpty() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(false, true); // Edge view only + + view.fill(); + + // Retain empty collection - should clear all edges + boolean changed = view.retainEdges(Collections.emptyList()); + + Assert.assertTrue(changed, "Retaining empty list should return true"); + Assert.assertEquals(view.getEdgeCount(), 0, "Should have no edges"); + } + + @Test + public void testRetainNodesWithMutualEdges() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStoreWithMutualEdge(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + view.fill(); + + EdgeImpl e0 = graphStore.getEdge("0"); + NodeImpl n1 = e0.getSource(); + NodeImpl n2 = e0.getTarget(); + + // Initial state: view has mutual edges + Assert.assertEquals(view.mutualEdgesCount, 1, "View should have mutual count of 1"); + Assert.assertEquals(view.getEdgeCount(), 2, "View should have 2 edges"); + + // Retain both nodes - mutual edges should remain + boolean changed = view.retainNodes(Arrays.asList(n1, n2)); + + Assert.assertFalse(changed, "Retaining all nodes should return false"); + Assert.assertEquals(view.mutualEdgesCount, 1, "Mutual edges should remain"); + Assert.assertEquals(view.getEdgeCount(), 2, "Should still have 2 edges"); + } + + @Test + public void testRetainEdgesWithMultipleTypes() { + GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(false, true); // Edge view only + + view.fill(); + + int type0Count = view.getEdgeCount(0); + int type1Count = view.getEdgeCount(1); + int type2Count = view.getEdgeCount(2); + + Assert.assertTrue(type0Count > 0, "Should have type 0 edges"); + Assert.assertTrue(type1Count > 0, "Should have type 1 edges"); + Assert.assertTrue(type2Count > 0, "Should have type 2 edges"); + + // Collect only type 0 edges to retain + List type0Edges = new ArrayList<>(); + for (Edge e : graphStore.getEdges().toArray()) { + if (e.getType() == 0) { + type0Edges.add(e); + } + } + + // Retain only type 0 edges + boolean changed = view.retainEdges(type0Edges); + + Assert.assertTrue(changed, "Should have removed edges"); + Assert.assertEquals(view.getEdgeCount(0), type0Count, "Should still have all type 0 edges"); + Assert.assertEquals(view.getEdgeCount(1), 0, "Should have no type 1 edges"); + Assert.assertEquals(view.getEdgeCount(2), 0, "Should have no type 2 edges"); + Assert.assertEquals(view.getEdgeCount(), type0Count, "Total should match type 0 count"); + } + + @Test + public void testRetainNodesWithMultipleEdgeTypes() { + GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + view.fill(); + + int initialType0Count = view.getEdgeCount(0); + int initialType1Count = view.getEdgeCount(1); + + // Retain subset of nodes + List nodesToRetain = new ArrayList<>(); + int count = 0; + for (Node n : graphStore.getNodes()) { + nodesToRetain.add(n); + count++; + if (count >= 5) { + break; // Keep first 5 nodes + } + } + + boolean changed = view.retainNodes(nodesToRetain); + + Assert.assertTrue(changed, "Should have removed nodes"); + Assert.assertEquals(view.getNodeCount(), 5, "Should have exactly 5 nodes"); + + // Edge counts should have decreased but type tracking should still be correct + int newType0Count = view.getEdgeCount(0); + int newType1Count = view.getEdgeCount(1); + + Assert.assertTrue(newType0Count <= initialType0Count, "Type 0 count should not increase"); + Assert.assertTrue(newType1Count <= initialType1Count, "Type 1 count should not increase"); + Assert.assertEquals(view.getEdgeCount(), newType0Count + newType1Count + view + .getEdgeCount(2), "Total edge count should match sum of types"); + } + + @Test + public void testRetainNodesLargeScale() { + // Test bulk operation performance with larger dataset + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + view.fill(); + int totalNodes = view.getNodeCount(); + + // Retain half the nodes + List nodesToRetain = new ArrayList<>(); + int count = 0; + for (Node n : graphStore.getNodes()) { + if (count % 2 == 0) { + nodesToRetain.add(n); + } + count++; + } + + boolean changed = view.retainNodes(nodesToRetain); + + Assert.assertTrue(changed, "Should have removed nodes"); + Assert.assertTrue(view.getNodeCount() <= totalNodes / 2 + 1, "Should have roughly half the nodes"); + Assert.assertTrue(view.getNodeCount() >= totalNodes / 2 - 1, "Should have roughly half the nodes"); + + // Verify all retained nodes are in the view + for (Node n : nodesToRetain) { + Assert.assertTrue(view.containsNode((NodeImpl) n), "Retained node should be in view"); + } + } + + @Test + public void testRetainEdgesOnlyView() { + // Test retain edges when nodeView=false, edgeView=true + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(false, true); + + view.fill(); + + List edgesToRetain = new ArrayList<>(); + int count = 0; + for (Edge e : graphStore.getEdges().toArray()) { + edgesToRetain.add(e); + count++; + if (count >= 10) { + break; + } + } + + boolean changed = view.retainEdges(edgesToRetain); + + Assert.assertTrue(changed, "Should have removed edges"); + Assert.assertEquals(view.getEdgeCount(), 10, "Should have exactly 10 edges"); + + for (Edge e : edgesToRetain) { + Assert.assertTrue(view.containsEdge((EdgeImpl) e), "Retained edge should be in view"); + } + } +} diff --git a/store/src/test/java/org/gephi/graph/impl/GraphViewStoreTest.java b/src/test/java/org/gephi/graph/impl/GraphViewStoreTest.java similarity index 77% rename from store/src/test/java/org/gephi/graph/impl/GraphViewStoreTest.java rename to src/test/java/org/gephi/graph/impl/GraphViewStoreTest.java index 12f4434d..cb19f934 100644 --- a/store/src/test/java/org/gephi/graph/impl/GraphViewStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/GraphViewStoreTest.java @@ -15,11 +15,11 @@ */ package org.gephi.graph.impl; -import org.gephi.graph.api.Interval; import org.gephi.graph.api.DirectedSubgraph; import org.gephi.graph.api.Edge; import org.gephi.graph.api.GraphModel; import org.gephi.graph.api.GraphView; +import org.gephi.graph.api.Interval; import org.gephi.graph.api.Node; import org.gephi.graph.api.Subgraph; import org.gephi.graph.api.UndirectedSubgraph; @@ -71,6 +71,14 @@ public void testDestroy() { Assert.assertTrue(view.isDestroyed()); } + @Test(expectedExceptions = IllegalArgumentException.class) + public void testDestroyMainView() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + + store.destroyView(graphStore.mainGraphView); + } + @Test(expectedExceptions = IllegalArgumentException.class) public void testDestroyTwice() { GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); @@ -199,7 +207,7 @@ public void testGetUndirectedGraphNull() { store.getUndirectedGraph(null); } - @Test(expectedExceptions = ClassCastException.class) + @Test(expectedExceptions = IllegalArgumentException.class) public void testGetViewAnonymousClass() { GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); GraphViewStore store = graphStore.viewStore; @@ -212,7 +220,7 @@ public GraphModel getGraphModel() { @Override public boolean isMainView() { - throw new UnsupportedOperationException("Not supported yet."); + return false; } @Override @@ -247,9 +255,7 @@ public void testDirectedEmptyView() { Assert.assertEquals(graph.getNodeCount(), 0); Assert.assertEquals(graph.getEdgeCount(), 0); - for (int i = 0; i < graphStore.edgeTypeStore.length; i++) { - Assert.assertEquals(graph.getEdgeCount(i), 0); - } + Assert.assertEquals(graph.getEdgeCount(0), 0); Assert.assertFalse(graph.getNodes().iterator().hasNext()); Assert.assertFalse(graph.getEdges().iterator().hasNext()); @@ -266,9 +272,7 @@ public void testUndirectedEmptyView() { Assert.assertEquals(graph.getNodeCount(), 0); Assert.assertEquals(graph.getEdgeCount(), 0); - for (int i = 0; i < graphStore.edgeTypeStore.length; i++) { - Assert.assertEquals(graph.getEdgeCount(i), 0); - } + Assert.assertEquals(graph.getEdgeCount(0), 0); Assert.assertFalse(graph.getNodes().iterator().hasNext()); Assert.assertFalse(graph.getEdges().iterator().hasNext()); @@ -421,4 +425,119 @@ public void testAddRemoveEdgeWithGarbage() { Assert.assertTrue(graphStore.addEdge(e)); Assert.assertTrue(graphStore.removeEdge(e)); } + + @Test + public void testDeepHashCodeAfterDestroy() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + + store.createView(); + GraphViewImpl view2 = store.createView(); + store.destroyView(view2); + + store.deepHashCode(); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testDestroyViewNull() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + + store.destroyView(null); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testDestroyViewForeignStore() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + + GraphStore graphStore2 = GraphGenerator.generateSmallGraphStore(); + GraphViewImpl foreignView = graphStore2.viewStore.createView(); + + store.destroyView(foreignView); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testContainsForeignViewImplementation() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + + store.contains(new GraphView() { + @Override + public GraphModel getGraphModel() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isMainView() { + return false; + } + + @Override + public boolean isNodeView() { + throw new UnsupportedOperationException(); + } + + @Override + public Interval getTimeInterval() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEdgeView() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDestroyed() { + throw new UnsupportedOperationException(); + } + }); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testSetVisibleViewForeignViewImplementation() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + + store.setVisibleView(new GraphView() { + @Override + public GraphModel getGraphModel() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isMainView() { + return false; + } + + @Override + public boolean isNodeView() { + throw new UnsupportedOperationException(); + } + + @Override + public Interval getTimeInterval() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEdgeView() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDestroyed() { + throw new UnsupportedOperationException(); + } + }); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testCreateViewCopyNull() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + GraphViewStore store = graphStore.viewStore; + + store.createView((GraphView) null); + } } diff --git a/src/test/java/org/gephi/graph/impl/IndexImplTest.java b/src/test/java/org/gephi/graph/impl/IndexImplTest.java new file mode 100644 index 00000000..91fc637b --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/IndexImplTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.gephi.graph.impl; + +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.Origin; +import org.gephi.graph.api.Table; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class IndexImplTest { + + @Test + public void testIndexName() { + TableImpl nodeTable = generateEmptyNodeTable(); + IndexImpl index = nodeTable.store.indexStore.mainIndex; + Assert.assertEquals(index.getIndexClass(), Node.class); + Assert.assertEquals(index.getIndexName(), "index_" + Node.class.getCanonicalName()); + } + + @Test + public void testAddColumn() { + TableImpl nodeTable = generateEmptyNodeTable(); + IndexImpl index = nodeTable.store.indexStore.mainIndex; + ColumnImpl col = new ColumnImpl("foo", String.class, "foo", null, Origin.DATA, true, false); + col.setStoreId(0); + + Assert.assertEquals(index.size(), GraphStoreConfiguration.NODE_DEFAULT_COLUMNS); + index.addColumn(col); + Assert.assertEquals(index.size(), 1 + GraphStoreConfiguration.NODE_DEFAULT_COLUMNS); + Assert.assertSame(index.getIndex(col).getColumn(), col); + } + + @Test + public void testHasColumn() { + TableImpl nodeTable = generateEmptyNodeTable(); + IndexImpl index = nodeTable.store.indexStore.mainIndex; + ColumnImpl col1 = new ColumnImpl("foo", String.class, "foo", null, Origin.DATA, true, false); + ColumnImpl col2 = new ColumnImpl("bar", String.class, "bar", null, Origin.DATA, false, false); + col1.setStoreId(0); + col2.setStoreId(1); + + Assert.assertFalse(index.hasColumn(col1)); + index.addColumn(col1); + index.addColumn(col2); + Assert.assertTrue(index.hasColumn(col1)); + Assert.assertTrue(index.hasColumn(col2)); + } + + @Test + public void testHasColumnDifferentIndex() { + TableImpl nodeTable = generateEmptyNodeTable(); + IndexImpl index1 = nodeTable.store.indexStore.mainIndex; + + TableImpl nodeTable2 = generateEmptyNodeTable(); + IndexImpl index2 = nodeTable2.store.indexStore.mainIndex; + + ColumnImpl col1 = new ColumnImpl("foo", String.class, "foo", null, Origin.DATA, true, false); + ColumnImpl col2 = new ColumnImpl("bar", String.class, "bar", null, Origin.DATA, true, false); + col1.setStoreId(0); + col2.setStoreId(0); + + index1.addColumn(col1); + index2.addColumn(col2); + Assert.assertFalse(index1.hasColumn(col2)); + Assert.assertFalse(index2.hasColumn(col1)); + } + + @Test + public void testAddAllColumns() { + TableImpl nodeTable = generateEmptyNodeTable(); + IndexImpl index = nodeTable.store.indexStore.mainIndex; + ColumnImpl col1 = new ColumnImpl("1", String.class, "1", null, Origin.DATA, true, false); + ColumnImpl col2 = new ColumnImpl("2", String.class, "2", null, Origin.DATA, false, false); + ColumnImpl col3 = new ColumnImpl("3", String.class, "3", null, Origin.DATA, true, false); + col1.setStoreId(0); + col2.setStoreId(1); + col3.setStoreId(2); + + index.addAllColumns(new ColumnImpl[] { col1, col2, col3 }); + Assert.assertEquals(index.size(), 3 + GraphStoreConfiguration.NODE_DEFAULT_COLUMNS); + } + + @Test + public void testDestroy() { + TableImpl nodeTable = generateEmptyNodeTable(); + IndexImpl index = nodeTable.store.indexStore.mainIndex; + ColumnImpl col1 = new ColumnImpl("1", String.class, "1", null, Origin.DATA, true, false); + ColumnImpl col2 = new ColumnImpl("2", String.class, "2", null, Origin.DATA, false, false); + col1.setStoreId(0); + col2.setStoreId(1); + + index.addAllColumns(new ColumnImpl[] { col1, col2 }); + index.destroy(); + Assert.assertEquals(index.size(), 0); + Assert.assertNull(index.getIndex(col1)); + Assert.assertNull(index.getIndex(col2)); + } + + @Test + public void testDefaultColumns() { + GraphStore graphStore = GraphGenerator.generateEmptyGraphStore(); + IndexImpl nodeIndex = graphStore.nodeTable.store.indexStore.mainIndex; + IndexImpl edgeIndex = graphStore.edgeTable.store.indexStore.mainIndex; + + Assert.assertNotNull(nodeIndex.getIndex(graphStore.getModel().defaultColumns().degree())); + Assert.assertNotNull(nodeIndex.getIndex(graphStore.getModel().defaultColumns().inDegree())); + Assert.assertNotNull(nodeIndex.getIndex(graphStore.getModel().defaultColumns().outDegree())); + Assert.assertNotNull(nodeIndex.getIndex(graphStore.getModel().defaultColumns().nodeId())); + Assert.assertNotNull(nodeIndex.getIndex(graphStore.getModel().defaultColumns().nodeLabel())); + Assert.assertNotNull(nodeIndex.getIndex(graphStore.getModel().defaultColumns().nodeTimeSet())); + + Assert.assertNotNull(edgeIndex.getIndex(graphStore.getModel().defaultColumns().edgeId())); + Assert.assertNotNull(edgeIndex.getIndex(graphStore.getModel().defaultColumns().edgeLabel())); + Assert.assertNotNull(edgeIndex.getIndex(graphStore.getModel().defaultColumns().edgeType())); + Assert.assertNotNull(edgeIndex.getIndex(graphStore.getModel().defaultColumns().edgeTimeSet())); + } + + private TableImpl generateEmptyNodeTable() { + GraphStore graphStore = GraphGenerator.generateEmptyGraphStore(); + return graphStore.nodeTable; + } +} diff --git a/store/src/test/java/org/gephi/graph/impl/IndexStoreTest.java b/src/test/java/org/gephi/graph/impl/IndexStoreTest.java similarity index 77% rename from store/src/test/java/org/gephi/graph/impl/IndexStoreTest.java rename to src/test/java/org/gephi/graph/impl/IndexStoreTest.java index 3943230e..ce18d9b0 100644 --- a/store/src/test/java/org/gephi/graph/impl/IndexStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/IndexStoreTest.java @@ -13,15 +13,17 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.gephi.graph.impl; import java.util.ArrayList; import java.util.List; import org.gephi.graph.api.Column; -import org.gephi.graph.api.Origin; +import org.gephi.graph.api.Edge; import org.gephi.graph.api.Graph; import org.gephi.graph.api.GraphView; import org.gephi.graph.api.Node; +import org.gephi.graph.api.Origin; import org.gephi.graph.api.Subgraph; import org.testng.Assert; import org.testng.annotations.Test; @@ -34,7 +36,16 @@ public void testEmpty() { ColumnStore columnStore = graphStore.nodeTable.store; IndexStore indexStore = columnStore.indexStore; IndexImpl mainIndex = indexStore.mainIndex; - Assert.assertEquals(mainIndex.size(), 0); + Assert.assertEquals(mainIndex.size(), GraphStoreConfiguration.NODE_DEFAULT_COLUMNS); + } + + @Test + public void testEmptyForEdge() { + GraphStore graphStore = new GraphStore(); + ColumnStore columnStore = graphStore.edgeTable.store; + IndexStore indexStore = columnStore.indexStore; + IndexImpl mainIndex = indexStore.mainIndex; + Assert.assertEquals(mainIndex.size(), GraphStoreConfiguration.EDGE_DEFAULT_COLUMNS); } @Test @@ -72,7 +83,7 @@ public void testIndexNodeNull() { NodeImpl n = new NodeImpl("0"); indexStore.index(n); - Assert.assertEquals(n.attributes.length, indexStore.columnStore.length); + Assert.assertEquals(n.getAttributes().length, indexStore.columnStore.length); } @Test @@ -102,10 +113,12 @@ public void testIndexNode() { Assert.assertTrue(mainIndex.values(col2).contains(20)); Assert.assertSame(getIterable(mainIndex.get(col1, "A"))[0], n); - Assert.assertNull(mainIndex.get(col1, "B")); + Assert.assertNotNull(mainIndex.get(col1, "B")); + Assert.assertFalse(mainIndex.get(col1, "B").iterator().hasNext()); Assert.assertSame(getIterable(mainIndex.get("foo", "A"))[0], n); - Assert.assertNull(mainIndex.get("foo", "B")); + Assert.assertNotNull(mainIndex.get("foo", "B")); + Assert.assertFalse(mainIndex.get("foo", "B").iterator().hasNext()); } @Test @@ -240,6 +253,82 @@ public void testClear() { Assert.assertTrue(mainIndex.values(col).isEmpty()); } + @Test + public void testNodePropertyIndices() { + GraphStore graphStore = new GraphStore(); + ColumnStore columnStore = graphStore.nodeTable.store; + IndexImpl mainIndex = columnStore.indexStore.mainIndex; + + Column idCol = columnStore.getColumnByIndex(GraphStoreConfiguration.ELEMENT_ID_INDEX); + Column labelCol = columnStore.getColumnByIndex(GraphStoreConfiguration.ELEMENT_LABEL_INDEX); + + ColumnIndexImpl idIndex = mainIndex.getIndex(idCol); + ColumnIndexImpl labelIndex = mainIndex.getIndex(labelCol); + + Assert.assertNotNull(idIndex); + Assert.assertNotNull(labelIndex); + + Node n1 = graphStore.factory.newNode("0"); + graphStore.addNode(n1); + Assert.assertEquals(mainIndex.count(idCol, "0"), 1); + + n1.setLabel("foo"); + Assert.assertEquals(mainIndex.count(labelCol, "foo"), 1); + } + + @Test + public void testEdgePropertyIndices() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + ColumnStore columnStore = graphStore.edgeTable.store; + IndexImpl mainIndex = columnStore.indexStore.mainIndex; + + Column idCol = columnStore.getColumnByIndex(GraphStoreConfiguration.ELEMENT_ID_INDEX); + Column labelCol = columnStore.getColumnByIndex(GraphStoreConfiguration.ELEMENT_LABEL_INDEX); + Column weigthCol = columnStore.getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + + ColumnIndexImpl idIndex = mainIndex.getIndex(idCol); + ColumnIndexImpl labelIndex = mainIndex.getIndex(labelCol); + ColumnIndexImpl weightIndex = mainIndex.getIndex(weigthCol); + + Assert.assertNotNull(idIndex); + Assert.assertNotNull(labelIndex); + Assert.assertNotNull(weightIndex); + + Assert.assertEquals(mainIndex.count(idCol, "0"), 1); + Edge e0 = graphStore.getEdge("0"); + e0.setLabel("foo"); + Assert.assertEquals(mainIndex.count(labelCol, "foo"), 1); + Assert.assertEquals(mainIndex.count(weigthCol, 1.0), 1); + } + + @Test + public void testEdgeWeigthSimple() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + ColumnStore columnStore = graphStore.edgeTable.store; + IndexImpl mainIndex = columnStore.indexStore.mainIndex; + + Column weigthCol = columnStore.getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + + Assert.assertEquals(mainIndex.getMinValue(weigthCol), 1.0); + Assert.assertEquals(mainIndex.getMaxValue(weigthCol), 1.0); + } + + @Test + public void testEdgeWeigthInView() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + ColumnStore columnStore = graphStore.edgeTable.store; + + GraphView view = graphStore.viewStore.createView(); + Subgraph graph = graphStore.viewStore.getGraph(view); + graph.fill(); + IndexImpl index = columnStore.indexStore.createViewIndex(graph); + + Column weigthCol = columnStore.getColumnByIndex(GraphStoreConfiguration.EDGE_WEIGHT_INDEX); + + Assert.assertEquals(index.getMinValue(weigthCol), 1.0); + Assert.assertEquals(index.getMaxValue(weigthCol), 1.0); + } + @Test public void testCreateViewIndex() { GraphStore graphStore = generateBasicGraphStoreWithColumns(); @@ -434,9 +523,9 @@ public void testClearElementWithView() { n1.clearAttributes(); - Assert.assertEquals(index.countElements(column), 0); - Assert.assertEquals(index.countValues(column), 0); - Assert.assertEquals(index.count(column, "bar"), 0); + Assert.assertEquals(index.countElements(column), 1); + Assert.assertEquals(index.countValues(column), 1); + Assert.assertEquals(index.count(column, null), 1); } @Test @@ -461,6 +550,38 @@ public void testClearInView() { Assert.assertEquals(index.count(column, "bar"), 0); } + @Test + public void testNullAddColumn() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + + ColumnImpl column = new ColumnImpl(graphStore.nodeTable, "foo", String.class, "Foo", null, Origin.DATA, true, + false); + graphStore.nodeTable.store.addColumn(column); + + IndexStore indexStore = graphStore.nodeTable.store.indexStore; + IndexImpl index = indexStore.mainIndex; + + Assert.assertEquals(index.countElements(column), graphStore.getNodeCount()); + Assert.assertEquals(index.countValues(column), 1); + Assert.assertEquals(index.count(column, null), graphStore.getNodeCount()); + } + + @Test + public void testDefaultAddColumn() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + + ColumnImpl column = new ColumnImpl(graphStore.nodeTable, "foo", String.class, "Foo", "bar", Origin.DATA, true, + false); + graphStore.nodeTable.store.addColumn(column); + + IndexStore indexStore = graphStore.nodeTable.store.indexStore; + IndexImpl index = indexStore.mainIndex; + + Assert.assertEquals(index.countElements(column), graphStore.getNodeCount()); + Assert.assertEquals(index.countValues(column), 1); + Assert.assertEquals(index.count(column, "bar"), graphStore.getNodeCount()); + } + // UTILITY private NodeImpl[] generateNodesWithUniqueAttributes(ColumnStore columnStore) { int count = 100; diff --git a/store/src/test/java/org/gephi/graph/impl/IntervalIndexImplTest.java b/src/test/java/org/gephi/graph/impl/IntervalIndexImplTest.java similarity index 89% rename from store/src/test/java/org/gephi/graph/impl/IntervalIndexImplTest.java rename to src/test/java/org/gephi/graph/impl/IntervalIndexImplTest.java index 14a29d82..ef0a53f0 100644 --- a/store/src/test/java/org/gephi/graph/impl/IntervalIndexImplTest.java +++ b/src/test/java/org/gephi/graph/impl/IntervalIndexImplTest.java @@ -31,8 +31,7 @@ public class IntervalIndexImplTest { @Test public void testGetMin() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timeStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timeStore.nodeIndexStore; @@ -73,8 +72,7 @@ public void testGetMinMaxWithView() { @Test public void testGetMax() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timeStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timeStore.nodeIndexStore; @@ -92,8 +90,7 @@ public void testGetMax() { @Test public void testGetMinWithInfinite() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timeStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timeStore.nodeIndexStore; @@ -107,8 +104,7 @@ public void testGetMinWithInfinite() { @Test public void testGetMaxWithInfinite() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timeStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timeStore.nodeIndexStore; @@ -122,8 +118,7 @@ public void testGetMaxWithInfinite() { @Test public void testGetElements() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timeStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timeStore.nodeIndexStore; @@ -170,8 +165,7 @@ public void testGetElements() { @Test public void testHasNodesEdgesEmpty() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timeStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timeStore.nodeIndexStore; @@ -180,8 +174,7 @@ public void testHasNodesEdgesEmpty() { @Test public void testHasNodes() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timeStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timeStore.nodeIndexStore; @@ -204,8 +197,7 @@ public void testHasNodes() { @Test public void testHasNodesClear() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timeStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timeStore.nodeIndexStore; diff --git a/store/src/test/java/org/gephi/graph/impl/IntervalIndexStoreTest.java b/src/test/java/org/gephi/graph/impl/IntervalIndexStoreTest.java similarity index 85% rename from store/src/test/java/org/gephi/graph/impl/IntervalIndexStoreTest.java rename to src/test/java/org/gephi/graph/impl/IntervalIndexStoreTest.java index 701d8c2c..1e38791b 100644 --- a/store/src/test/java/org/gephi/graph/impl/IntervalIndexStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/IntervalIndexStoreTest.java @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.gephi.graph.impl; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -260,8 +261,7 @@ public void testRemoveElement() { @Test public void testIndexNode() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timestampStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timestampStore.nodeIndexStore; @@ -280,8 +280,7 @@ public void testIndexNode() { @Test public void testIndexNodeAdd() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timestampStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timestampStore.nodeIndexStore; @@ -300,8 +299,7 @@ public void testIndexNodeAdd() { @Test public void testClearNode() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timestampStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timestampStore.nodeIndexStore; @@ -322,8 +320,7 @@ public void testClearNode() { @Test public void testClearNodeWithAttributes() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timestampStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timestampStore.nodeIndexStore; @@ -340,8 +337,7 @@ public void testClearNodeWithAttributes() { @Test public void testClearRemove() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timestampStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timestampStore.nodeIndexStore; @@ -361,8 +357,7 @@ public void testClearRemove() { @Test public void testAddAfterAdd() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timestampStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timestampStore.nodeIndexStore; @@ -382,8 +377,7 @@ public void testAddAfterAdd() { @Test public void testRemoveAfterAdd() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timestampStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timestampStore.nodeIndexStore; @@ -408,8 +402,7 @@ public void testRemoveAfterAdd() { @Test public void testSetAttribute() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timestampStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timestampStore.nodeIndexStore; @@ -434,10 +427,38 @@ public void testSetAttribute() { Assert.assertFalse(store.contains(new Interval(3.0, 4.0))); } + @Test + public void testSetAttributeIntervalCounts() { + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); + GraphModelImpl graphModel = new GraphModelImpl(config); + IntervalIndexStore store = (IntervalIndexStore) graphModel.store.timeStore.nodeIndexStore; + + Column col = graphModel.store.nodeTable.addColumn("col", IntervalStringMap.class); + NodeImpl nodeImpl = (NodeImpl) graphModel.store.factory.newNode("0"); + graphModel.store.addNode(nodeImpl); + + Interval first = new Interval(1.0, 2.0); + Interval second = new Interval(3.0, 4.0); + + nodeImpl.setAttribute(col, "foo", first); + nodeImpl.setAttribute(col, "bar", second); + // Overwriting an existing interval is not a new reference + nodeImpl.setAttribute(col, "baz", first); + + Assert.assertEquals(store.size(), 2); + Assert.assertEquals(store.countMap[(Integer) store.timeSortedMap.get(first)], 1); + Assert.assertEquals(store.countMap[(Integer) store.timeSortedMap.get(second)], 1); + + nodeImpl.removeAttribute(col, first); + Assert.assertFalse(store.contains(first)); + nodeImpl.removeAttribute(col); + Assert.assertEquals(store.size(), 0); + Assert.assertFalse(store.contains(second)); + } + @Test public void testRemoveAttributeTimestamp() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timestampStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timestampStore.nodeIndexStore; @@ -454,8 +475,7 @@ public void testRemoveAttributeTimestamp() { @Test public void testAddWithAttribute() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphStore graphStore = new GraphModelImpl(config).store; TimeStore timestampStore = graphStore.timeStore; IntervalIndexStore store = (IntervalIndexStore) timestampStore.nodeIndexStore; @@ -491,8 +511,7 @@ public void testCreateView() { @Test(expectedExceptions = IllegalArgumentException.class) public void testCreateViewMainView() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timestampStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timestampStore.nodeIndexStore; @@ -501,8 +520,7 @@ public void testCreateViewMainView() { @Test(expectedExceptions = IllegalArgumentException.class) public void testDeleteViewMainView() { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); TimeStore timestampStore = graphModel.store.timeStore; IntervalIndexStore store = (IntervalIndexStore) timestampStore.nodeIndexStore; @@ -609,7 +627,7 @@ public void testClearElementWithView() { Graph graph = graphStore.viewStore.getGraph(view); view.fill(); TimeIndexImpl index = store.createViewIndex(graph); - n1.clearAttributes(); + n1.destroyAttributes(); Assert.assertFalse(index.hasElements()); } @@ -630,6 +648,33 @@ public void testClearViewWithView() { Assert.assertFalse(index.hasElements()); } + @Test + public void testGetIndexViewNotIndexed() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(TimeRepresentation.INTERVAL); + IntervalIndexStore store = new IntervalIndexStore<>(Node.class, null, false); + GraphView view = graphStore.viewStore.createView(); + Graph graph = graphStore.viewStore.getGraph(view); + Assert.assertNull(store.getIndex(graph)); + } + + @Test + public void testClearInViewElementNotInView() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(TimeRepresentation.INTERVAL); + NodeImpl n1 = graphStore.getNode("1"); + n1.addInterval(new Interval(1.0, 2.0)); + + IntervalIndexStore store = (IntervalIndexStore) graphStore.timeStore.nodeIndexStore; + + GraphViewImpl view = graphStore.viewStore.createView(); + Graph graph = graphStore.viewStore.getGraph(view); + store.createViewIndex(graph); + + // n1 has interval [1,2] in timeSortedMap but was never added to the (empty) + // view index + // clearInView previously threw ArrayIndexOutOfBoundsException + store.clearInView(n1, view); + } + // UTILITY private Object[] getArrayFromIterable(Iterable iterable) { List list = new ArrayList<>(); diff --git a/store/src/test/java/org/gephi/graph/impl/IntervalTest.java b/src/test/java/org/gephi/graph/impl/IntervalTest.java similarity index 100% rename from store/src/test/java/org/gephi/graph/impl/IntervalTest.java rename to src/test/java/org/gephi/graph/impl/IntervalTest.java diff --git a/store/src/test/java/org/gephi/graph/impl/IntervalTreeMapTest.java b/src/test/java/org/gephi/graph/impl/IntervalTreeMapTest.java similarity index 100% rename from store/src/test/java/org/gephi/graph/impl/IntervalTreeMapTest.java rename to src/test/java/org/gephi/graph/impl/IntervalTreeMapTest.java diff --git a/store/src/test/java/org/gephi/graph/impl/IntervalsParserTest.java b/src/test/java/org/gephi/graph/impl/IntervalsParserTest.java similarity index 78% rename from store/src/test/java/org/gephi/graph/impl/IntervalsParserTest.java rename to src/test/java/org/gephi/graph/impl/IntervalsParserTest.java index 84b35d39..cef3d1ad 100644 --- a/store/src/test/java/org/gephi/graph/impl/IntervalsParserTest.java +++ b/src/test/java/org/gephi/graph/impl/IntervalsParserTest.java @@ -17,6 +17,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.format.DateTimeParseException; import java.util.Date; import java.util.TimeZone; import org.gephi.graph.api.Interval; @@ -86,23 +87,28 @@ public void testParseIntervalSet() throws ParseException { // Doubles: assertEquals(buildIntervalSet(new Interval(1, 2)), IntervalsParser.parseIntervalSet("[1, 2]")); - assertEquals(buildIntervalSet(new Interval(1, 2), new Interval(2, 3)), IntervalsParser.parseIntervalSet("<[1, 2]; [2,3]>")); - assertEquals(buildIntervalSet(new Interval(1, 2), new Interval(2, 31)), IntervalsParser.parseIntervalSet("<[1, 2]; [2,31.]>")); - assertEquals(buildIntervalSet(new Interval(1, 2), new Interval(2, 31)), IntervalsParser.parseIntervalSet("<[1, 2]; [2,31.0)")); - assertEquals(buildIntervalSet(new Interval(-5000, -1), new Interval(0, 0.5)), IntervalsParser.parseIntervalSet("(-5000,-1][0, .5)")); - assertEquals(buildIntervalSet(new Interval(-5000, -1), new Interval(0, 0.5)), IntervalsParser.parseIntervalSet("(-5e3,-1)(0, .5)")); + assertEquals(buildIntervalSet(new Interval(1, 2), new Interval(2, 3)), IntervalsParser + .parseIntervalSet("<[1, 2]; [2,3]>")); + assertEquals(buildIntervalSet(new Interval(1, 2), new Interval(2, 31)), IntervalsParser + .parseIntervalSet("<[1, 2]; [2,31.]>")); + assertEquals(buildIntervalSet(new Interval(1, 2), new Interval(2, 31)), IntervalsParser + .parseIntervalSet("<[1, 2]; [2,31.0)")); + assertEquals(buildIntervalSet(new Interval(-5000, -1), new Interval(0, 0.5)), IntervalsParser + .parseIntervalSet("(-5000,-1][0, .5)")); + assertEquals(buildIntervalSet(new Interval(-5000, -1), new Interval(0, 0.5)), IntervalsParser + .parseIntervalSet("(-5e3,-1)(0, .5)")); // Dates: assertEquals(buildIntervalSet(new Interval(parseDateIntoTimestamp("2015-01-01"), parseDateIntoTimestamp("2015-01-31"))), IntervalsParser.parseIntervalSet("[2015-01-01, 2015-01-31]")); - assertEquals(buildIntervalSet(new Interval(parseDateIntoTimestamp("2015-01-01"), - parseDateIntoTimestamp("2015-01-31"))), IntervalsParser.parseIntervalSet("[2015-01, 2015-01-31]")); // Date times: assertEquals(buildIntervalSet(new Interval(parseDateTimeIntoTimestamp("2015-01-01 21:12:05"), - parseDateTimeIntoTimestamp("2015-01-02 00:00:00"))), IntervalsParser.parseIntervalSet("[2015-01-01T21:12:05, 2015-01-02]")); + parseDateTimeIntoTimestamp("2015-01-02 00:00:00"))), IntervalsParser + .parseIntervalSet("[2015-01-01T21:12:05, 2015-01-02]")); assertEquals(buildIntervalSet(new Interval(parseDateTimeMillisIntoTimestamp("2015-01-01 21:12:05.121"), - parseDateTimeMillisIntoTimestamp("2015-01-02 00:00:01.999"))), IntervalsParser.parseIntervalSet("[2015-01-01T21:12:05.121, 2015-01-02T00:00:01.999]")); + parseDateTimeMillisIntoTimestamp("2015-01-02 00:00:01.999"))), IntervalsParser + .parseIntervalSet("[2015-01-01T21:12:05.121, 2015-01-02T00:00:01.999]")); } @Test(expectedExceptions = IllegalArgumentException.class) @@ -164,7 +170,8 @@ public void testParseIntervalMapString() { expected.put(new Interval(5, 6), "Value 3"); expected.put(new Interval(6, 7), " Value 4 "); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(String.class, "[1, 2, Value1]; [3, 5, 'Value2']; [5, 6, Value 3]; [6, 7, \" Value 4 \"]")); + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(String.class, "[1, 2, Value1]; [3, 5, 'Value2']; [5, 6, Value 3]; [6, 7, \" Value 4 \"]")); } @Test @@ -175,8 +182,10 @@ public void testParseIntervalMapByte() { expected.put(new Interval(5, 6), (byte) 3); expected.put(new Interval(6, 7), (byte) 4); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(Byte.class, "[1, 2, 1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]")); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(byte.class, "[1, 2, 1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]")); + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(Byte.class, "[1, 2, 1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]")); + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(byte.class, "[1, 2, 1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]")); } @Test @@ -187,12 +196,14 @@ public void testParseIntervalMapShort() { expected.put(new Interval(5, 6), (short) 3); expected.put(new Interval(6, 7), (short) 4); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(Short.class, "[1, 2, 1.1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]"));// Decimals - // are - // ignored - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(short.class, "[1, 2, 1.1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]"));// Decimals - // are - // ignored + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(Short.class, "[1, 2, 1.1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]"));// Decimals + // are + // ignored + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(short.class, "[1, 2, 1.1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]"));// Decimals + // are + // ignored } @Test @@ -203,12 +214,14 @@ public void testParseIntervalMapInteger() { expected.put(new Interval(5, 6), 3); expected.put(new Interval(6, 7), 4); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(Integer.class, "[1, 2, 1.]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]"));// Decimals - // are - // ignored - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(int.class, "[1, 2, 1.]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]"));// Decimals - // are - // ignored + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(Integer.class, "[1, 2, 1.]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]"));// Decimals + // are + // ignored + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(int.class, "[1, 2, 1.]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]"));// Decimals + // are + // ignored } @Test @@ -219,12 +232,14 @@ public void testParseIntervalMapLong() { expected.put(new Interval(5, 6), 3l); expected.put(new Interval(6, 7), 4l); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(Long.class, "[1, 2, 1.0]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]"));// Decimals - // are - // ignored - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(long.class, "[1, 2, 1.0]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]"));// Decimals - // are - // ignored + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(Long.class, "[1, 2, 1.0]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]"));// Decimals + // are + // ignored + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(long.class, "[1, 2, 1.0]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]"));// Decimals + // are + // ignored } @Test @@ -235,8 +250,10 @@ public void testParseIntervalMapFloat() { expected.put(new Interval(5, 6), 3f); expected.put(new Interval(6, 7), 4f); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(Float.class, "[1, 2, 1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]")); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(float.class, "[1, 2, 1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]")); + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(Float.class, "[1, 2, 1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]")); + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(float.class, "[1, 2, 1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]")); } @Test @@ -247,8 +264,10 @@ public void testParseIntervalMapDouble() { expected.put(new Interval(5, 6), 3d); expected.put(new Interval(6, 7), 4d); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(Double.class, "[1, 2, 1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]")); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(double.class, "[1, 2, 1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]")); + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(Double.class, "[1, 2, 1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]")); + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(double.class, "[1, 2, 1]; [3, 5, 2]; [5, 6, '3']; [6, 7, \"4\"]")); } @Test @@ -259,8 +278,10 @@ public void testParseIntervalMapBoolean() { expected.put(new Interval(5, 6), false); expected.put(new Interval(6, 7), true); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(Boolean.class, "[1, 2, true]; [3, 5, false]; [5, 6, '0']; [6, 7, \"1\"]")); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(boolean.class, "[1, 2, true]; [3, 5, false]; [5, 6, 0]; [6, 7, 1]")); + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(Boolean.class, "[1, 2, true]; [3, 5, false]; [5, 6, '0']; [6, 7, \"1\"]")); + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(boolean.class, "[1, 2, true]; [3, 5, false]; [5, 6, 0]; [6, 7, 1]")); } @Test @@ -271,8 +292,10 @@ public void testParseIntervalMapChar() { expected.put(new Interval(5, 6), 'c'); expected.put(new Interval(6, 7), 'd'); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(Character.class, "[1, 2, a]; [3, 5, b]; [5, 6, 'c']; [6, 7, \"d\"]")); - assertEqualIntervalMaps(expected, IntervalsParser.parseIntervalMap(char.class, "[1, 2, a]; [3, 5, b]; [5, 6, 'c']; [6, 7, \"d\"]")); + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(Character.class, "[1, 2, a]; [3, 5, b]; [5, 6, 'c']; [6, 7, \"d\"]")); + assertEqualIntervalMaps(expected, IntervalsParser + .parseIntervalMap(char.class, "[1, 2, a]; [3, 5, b]; [5, 6, 'c']; [6, 7, \"d\"]")); } @Test(expectedExceptions = IllegalArgumentException.class) diff --git a/store/src/test/java/org/gephi/graph/impl/LongPackerTest.java b/src/test/java/org/gephi/graph/impl/LongPackerTest.java similarity index 100% rename from store/src/test/java/org/gephi/graph/impl/LongPackerTest.java rename to src/test/java/org/gephi/graph/impl/LongPackerTest.java diff --git a/src/test/java/org/gephi/graph/impl/NodeImplTest.java b/src/test/java/org/gephi/graph/impl/NodeImplTest.java new file mode 100644 index 00000000..b0b4bd6d --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/NodeImplTest.java @@ -0,0 +1,26 @@ +package org.gephi.graph.impl; + +import org.gephi.graph.api.Configuration; +import org.gephi.graph.api.Node; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class NodeImplTest { + + @Test + public void testProperties() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + Node n = graphStore.getNode("1"); + Assert.assertNotNull(n.getTextProperties()); + Assert.assertNotNull(n.getColor()); + Assert.assertEquals(n.alpha(), 1f); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testPropertiesDisabled() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(Configuration.builder().enableSpatialIndex(false) + .enableNodeProperties(false).build()); + Node n = graphStore.getNode("1"); + Assert.assertNull(n.getColor()); + } +} diff --git a/store/src/test/java/org/gephi/graph/impl/NodeStoreTest.java b/src/test/java/org/gephi/graph/impl/NodeStoreTest.java similarity index 74% rename from store/src/test/java/org/gephi/graph/impl/NodeStoreTest.java rename to src/test/java/org/gephi/graph/impl/NodeStoreTest.java index 4b1c6a1a..d88b9cab 100644 --- a/store/src/test/java/org/gephi/graph/impl/NodeStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/NodeStoreTest.java @@ -20,10 +20,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; +import java.util.Spliterator; +import java.util.stream.Collectors; import org.gephi.graph.api.Node; import org.testng.Assert; import org.testng.annotations.Test; @@ -95,6 +98,19 @@ public void testAddOtherStore() { nodeStore2.add(node); } + @Test + public void testMaxStoreId() { + NodeStore nodeStore = new NodeStore(); + Assert.assertEquals(nodeStore.maxStoreId(), 0); + NodeImpl node1 = new NodeImpl("0"); + NodeImpl node2 = new NodeImpl("1"); + nodeStore.add(node1); + Assert.assertEquals(nodeStore.maxStoreId(), 1); + nodeStore.add(node2); + Assert.assertEquals(nodeStore.maxStoreId(), 2); + Assert.assertEquals(node2.getStoreId(), 1); + } + @Test public void testGet() { NodeStore nodeStore = new NodeStore(); @@ -317,6 +333,14 @@ public void testContainsAll() { Assert.assertTrue(nodeStore.containsAll(Arrays.asList(nodes))); } + @Test + public void testContainsAllEmpty() { + NodeStore nodeStore = new NodeStore(); + NodeImpl[] nodes = new NodeImpl[] { new NodeImpl("0"), new NodeImpl("1") }; + nodeStore.addAll(Arrays.asList(nodes)); + Assert.assertTrue(nodeStore.containsAll(new java.util.ArrayList<>())); + } + @Test public void testIterator() { NodeStore nodeStore = new NodeStore(); @@ -502,6 +526,167 @@ public void testDictionaryDuplicate() { nodeStore.add(node2); } + @Test + public void testSpliteratorCoversAll() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + nodeStore.addAll(nodes); + + List seen = new ArrayList<>(); + Spliterator sp = nodeStore.spliterator(); + Assert.assertEquals(sp.estimateSize(), nodes.size()); + sp.forEachRemaining(e -> seen.add((NodeImpl) e)); + + Assert.assertEquals(seen, nodes); + } + + @Test + public void testSpliteratorSizeReduce() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateSmallNodeList()); + nodeStore.addAll(nodes); + + Spliterator sp = nodeStore.spliterator(); + long size = sp.estimateSize(); + sp.tryAdvance(n -> { + }); + Assert.assertEquals(sp.estimateSize(), size - 1); + } + + @Test + public void testSpliteratorSizeReduceWithGarbage() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateSmallNodeList()); + nodeStore.addAll(nodes); + nodeStore.remove(nodes.get(0)); + + Spliterator sp = nodeStore.spliterator(); + Assert.assertEquals(sp.estimateSize(), nodes.size() - 1); + + // Last node + nodeStore.remove(nodes.get(nodes.size() - 1)); + sp = nodeStore.spliterator(); + Assert.assertEquals(sp.estimateSize(), nodes.size() - 2); + } + + @Test + public void testSpliteratorParallel() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + nodeStore.addAll(nodes); + + List seen = nodeStore.parallelStream().collect(Collectors.toList()); + + Assert.assertEquals(seen, nodes); + } + + @Test + public void testSpliteratorEmpty() { + NodeStore nodeStore = new NodeStore(); + + Assert.assertEquals(nodeStore.parallelStream().count(), 0); + Assert.assertEquals(nodeStore.spliterator().estimateSize(), 0); + } + + @Test + public void testSpliteratorEmptyAfterRemove() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + nodeStore.addAll(nodes); + nodeStore.removeAll(nodes); + + Assert.assertEquals(nodeStore.parallelStream().count(), 0); + Assert.assertEquals(nodeStore.spliterator().estimateSize(), 0); + } + + @Test + public void testSpliteratorParallelLarge() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays + .asList(GraphGenerator.generateNodeList(GraphStoreConfiguration.NODESTORE_BLOCK_SIZE * 4 + 10)); + nodeStore.addAll(nodes); + + List seen = nodeStore.parallelStream().collect(Collectors.toList()); + + Assert.assertEquals(seen, nodes); + } + + @Test + public void testSpliteratorParallelAfterRemove() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateSmallNodeList()); + nodeStore.addAll(nodes); + nodeStore.remove(nodes.get(0)); + + List seen = nodeStore.parallelStream().collect(Collectors.toList()); + + Assert.assertEquals(seen, nodes.subList(1, nodes.size())); + } + + @Test(expectedExceptions = ConcurrentModificationException.class) + public void testSpliteratorFailFastOnAdd() { + NodeStore store = new NodeStore(null, null, null, null, new GraphVersion(null)); + store.add(new NodeImpl("a")); + Spliterator sp = store.spliterator(); + store.add(new NodeImpl("b")); + sp.tryAdvance(x -> { + }); + } + + @Test + public void testParallelStreamCount() { + NodeStore store = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + store.addAll(nodes); + + long count = store.parallelStream().count(); + Assert.assertEquals(count, store.size()); + } + + @Test + public void testParallelStreamForEach() { + NodeStore store = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + store.addAll(nodes); + + Set set = Collections.synchronizedSet(new HashSet<>()); + store.parallelStream().forEach(set::add); + Assert.assertEquals(set, store.toSet()); + } + + @Test + public void testParallelStreamForEachOrdered() { + NodeStore store = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + store.addAll(nodes); + + Set set = Collections.synchronizedSet(new HashSet<>()); + store.parallelStream().forEachOrdered(set::add); + Assert.assertEquals(set, store.toSet()); + } + + @Test + public void testParallelStreamForEachOrderedAfterRemove() { + NodeStore store = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + store.addAll(nodes); + + Random random = new Random(); + for (int i = 0; i < nodes.size() / 5; i++) { + int r = random.nextInt(store.maxStoreId()); + Node n = store.getForGetByStoreId(r); + if (n != null) { + store.remove(n); + } + } + + Set set = Collections.synchronizedSet(new HashSet<>()); + store.parallelStream().forEachOrdered(set::add); + Assert.assertEquals(set, store.toSet()); + } + + // Utils + private void testContainsOnly(NodeStore store, List list) { for (NodeImpl n : list) { Assert.assertTrue(store.contains(n)); diff --git a/src/test/java/org/gephi/graph/impl/NodesQuadTreeTest.java b/src/test/java/org/gephi/graph/impl/NodesQuadTreeTest.java new file mode 100644 index 00000000..dc48e002 --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/NodesQuadTreeTest.java @@ -0,0 +1,841 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Random; +import java.util.stream.Collectors; +import org.gephi.graph.api.Configuration; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.EdgeIterable; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.NodeIterable; +import org.gephi.graph.api.Rect2D; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class NodesQuadTreeTest { + + private static final float BOUNDS = 1e6f; + private static final Rect2D BOUNDS_RECT = new Rect2D(-BOUNDS, -BOUNDS, BOUNDS, BOUNDS); + + @Test + public void testBoundaries() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + Assert.assertEquals(q.quadRect(), BOUNDS_RECT); + } + + @Test + public void testGetAllNodesEmpty() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + Assert.assertTrue(q.getAllNodes().toCollection().isEmpty()); + Assert.assertEquals(q.getAllNodes().toArray().length, 0); + Assert.assertEquals(q.getNodeCount(false), 1); + } + + @Test + public void testGetNodeCount() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + Assert.assertEquals(q.getNodeCount(false), 1); + Assert.assertEquals(q.getNodeCount(true), 0); + } + + @Test + public void testGetNodesEmpty() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + Assert.assertTrue(q.getNodes(new Rect2D(-1, -1, 1, 1)).toCollection().isEmpty()); + } + + @Test + public void testRemoveEmpty() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + NodeImpl node = new NodeImpl("0"); + Assert.assertFalse(q.removeNode(node)); + Assert.assertEquals(q.getNodeCount(true), 0); + } + + @Test + public void testAddNode() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + NodeImpl node = new NodeImpl("0"); + Assert.assertTrue(q.addNode(node)); + Assert.assertNotNull(node.getSpatialData().quadTreeNode); + Assert.assertEquals(q.getNodeCount(true), 1); + } + + @Test + public void testAddNodeTwice() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + NodeImpl node = new NodeImpl("0"); + q.addNode(node); + Assert.assertFalse(q.addNode(node)); + } + + @Test + public void testClear() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + NodeImpl node = new NodeImpl("0"); + q.addNode(node); + q.clear(); + Assert.assertNull(node.getSpatialData().quadTreeNode); + Assert.assertTrue(q.getAllNodes().toCollection().isEmpty()); + Assert.assertFalse(q.removeNode(node)); + Assert.assertEquals(q.getNodeCount(true), 0); + } + + @Test + public void testCount() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + Assert.assertEquals(q.getObjectCount(), 0); + NodeImpl node = new NodeImpl("0"); + q.addNode(node); + Assert.assertEquals(q.getObjectCount(), 1); + q.clear(); + Assert.assertEquals(q.getObjectCount(), 0); + } + + @Test + public void testUpdate() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + Assert.assertEquals(q.getObjectCount(), 0); + NodeImpl node = new NodeImpl("0"); + q.addNode(node); + Assert.assertTrue(q.updateNode(node, -100, -50, -50, 0)); + } + + @Test + public void testDepthZero() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + Assert.assertEquals(q.getDepth(), 0); + } + + @Test + public void testDepth() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + Random random = new Random(42L); + for (int i = 0; i <= GraphStoreConfiguration.SPATIAL_INDEX_MAX_OBJECTS_PER_NODE; i++) { + NodeImpl node = new NodeImpl(String.valueOf(i)); + node.setPosition(random.nextInt((int) BOUNDS * 2) - BOUNDS, random.nextInt((int) BOUNDS * 2) - BOUNDS); + q.addNode(node); + } + Assert.assertTrue(q.getDepth() >= 1); + Assert.assertEquals(q.getNodeCount(true), 4); + Assert.assertEquals(q.getNodeCount(false), 5); + } + + @Test + public void testGetAll() { + final NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + NodeImpl n1 = new NodeImpl("1"); + n1.setPosition(100, 100); + NodeImpl n2 = new NodeImpl("2"); + n2.setPosition(0, 0); + NodeImpl n3 = new NodeImpl("3"); + n2.setPosition(-100, -100); + + q.addNode(n1); + q.addNode(n2); + q.addNode(n3); + + Collection all = q.getAllNodes().toCollection(); + Assert.assertEquals(all.size(), 3); + + Collection rectContainingAll = q.getNodes(BOUNDS_RECT).toCollection(); + Assert.assertEquals(rectContainingAll, all); + + Collection bigRectContainingAll = q.getNodes(new Rect2D(-BOUNDS * 2, -BOUNDS * 2, BOUNDS, BOUNDS)) + .toCollection(); + Assert.assertEquals(bigRectContainingAll, all); + } + + @Test + public void testOutOfBoundsStillWorks() { + final NodesQuadTree q = new NodesQuadTree(new Rect2D(0, 0, 10, 10)); + + NodeImpl n1 = new NodeImpl("1"); + n1.setPosition(100, 100); + n1.setSize(10); + + NodeImpl n2 = new NodeImpl("2"); + n2.setPosition(0, 0); + n2.setSize(5); + + NodeImpl n3 = new NodeImpl("3"); + n3.setPosition(-100, -100); + n3.setSize(3); + + q.addNode(n1); + q.addNode(n2); + q.addNode(n3); + + Collection all = q.getAllNodes().toCollection(); + Assert.assertEquals(all.size(), 3); + all = q.getAllNodes().stream().collect(Collectors.toList()); + Assert.assertEquals(all.size(), 3); + + assertEmpty(q.getNodes(new Rect2D(80, 80, 89.99f, 89.99f))); + + assertSameSet(q.getNodes(new Rect2D(95, 95, 99, 99)), n1); + assertSameSet(q.getNodes(new Rect2D(0, 0, 101, 101)), n1, n2); + assertSameSet(q.getNodes(new Rect2D(4, 4, 91, 91)), n1, n2); + } + + @Test + public void testGetZone1() { + final NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + NodeImpl n1 = new NodeImpl("1"); + n1.setPosition(100, 100); + n1.setSize(10); + + NodeImpl n2 = new NodeImpl("2"); + n2.setPosition(0, 0); + n2.setSize(5); + + NodeImpl n3 = new NodeImpl("3"); + n3.setPosition(-100, -100); + n3.setSize(3); + + q.addNode(n1); + q.addNode(n2); + q.addNode(n3); + + assertEmpty(q.getNodes(new Rect2D(80, 80, 89.99f, 89.99f))); + + assertSameSet(q.getNodes(new Rect2D(95, 95, 99, 99)), n1); + assertSameSet(q.getNodes(new Rect2D(0, 0, 101, 101)), n2, n1); + assertSameSet(q.getNodes(new Rect2D(4, 4, 91, 91)), n2, n1); + } + + @Test + public void testGetZone2() { + final NodesQuadTree q = new NodesQuadTree(new Rect2D(120, -120, 120, 120)); + + NodeImpl n1 = new NodeImpl("1"); + n1.setPosition(100, 100); + n1.setSize(10); + + NodeImpl n2 = new NodeImpl("2"); + n2.setPosition(0, 0); + n2.setSize(5); + + NodeImpl n3 = new NodeImpl("3"); + n3.setPosition(-100, -100); + n3.setSize(3); + + q.addNode(n1); + q.addNode(n2); + q.addNode(n3); + + assertEmpty(q.getNodes(new Rect2D(80, 80, 89.99f, 89.99f))); + + assertSameSet(q.getNodes(new Rect2D(95, 95, 99, 99)), n1); + assertSameSet(q.getNodes(new Rect2D(0, 0, 101, 101)), n1, n2); + assertSameSet(q.getNodes(new Rect2D(4, 4, 91, 91)), n1, n2); + } + + @Test + public void testGetBoundariesEmpty() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, Float.NEGATIVE_INFINITY); + Assert.assertEquals(boundaries.minY, Float.NEGATIVE_INFINITY); + Assert.assertEquals(boundaries.maxX, Float.POSITIVE_INFINITY); + Assert.assertEquals(boundaries.maxY, Float.POSITIVE_INFINITY); + } + + @Test + public void testGetBoundariesSingleNode() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + NodeImpl node = new NodeImpl("0"); + node.setPosition(100, 200); + node.setSize(10); + + q.addNode(node); + + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, 90f); // x - size + Assert.assertEquals(boundaries.minY, 190f); // y - size + Assert.assertEquals(boundaries.maxX, 110f); // x + size + Assert.assertEquals(boundaries.maxY, 210f); // y + size + } + + @Test + public void testGetBoundariesMultipleNodes() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + NodeImpl n1 = new NodeImpl("1"); + n1.setPosition(0, 0); + n1.setSize(5); + + NodeImpl n2 = new NodeImpl("2"); + n2.setPosition(100, 200); + n2.setSize(10); + + NodeImpl n3 = new NodeImpl("3"); + n3.setPosition(-50, -100); + n3.setSize(15); + + q.addNode(n1); + q.addNode(n2); + q.addNode(n3); + + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, -65f); // n3: -50 - 15 + Assert.assertEquals(boundaries.minY, -115f); // n3: -100 - 15 + Assert.assertEquals(boundaries.maxX, 110f); // n2: 100 + 10 + Assert.assertEquals(boundaries.maxY, 210f); // n2: 200 + 10 + } + + @Test + public void testGetBoundariesAfterClear() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + NodeImpl node = new NodeImpl("0"); + node.setPosition(100, 200); + node.setSize(10); + q.addNode(node); + + // Should have boundaries initially + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertNotEquals(boundaries.minX, Float.NEGATIVE_INFINITY); + + // After clear, should return empty rectangle + q.clear(); + boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, Float.NEGATIVE_INFINITY); + Assert.assertEquals(boundaries.minY, Float.NEGATIVE_INFINITY); + Assert.assertEquals(boundaries.maxX, Float.POSITIVE_INFINITY); + Assert.assertEquals(boundaries.maxY, Float.POSITIVE_INFINITY); + } + + @Test + public void testGetBoundariesAfterRemove() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + NodeImpl n1 = new NodeImpl("1"); + n1.setPosition(0, 0); + n1.setSize(5); + + NodeImpl n2 = new NodeImpl("2"); + n2.setPosition(100, 200); + n2.setSize(10); + + q.addNode(n1); + q.addNode(n2); + + // Remove one node + q.removeNode(n1); + + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, 90f); // Only n2 remains + Assert.assertEquals(boundaries.minY, 190f); + Assert.assertEquals(boundaries.maxX, 110f); + Assert.assertEquals(boundaries.maxY, 210f); + + // Remove last node + q.removeNode(n2); + boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, Float.NEGATIVE_INFINITY); + Assert.assertEquals(boundaries.minY, Float.NEGATIVE_INFINITY); + Assert.assertEquals(boundaries.maxX, Float.POSITIVE_INFINITY); + Assert.assertEquals(boundaries.maxY, Float.POSITIVE_INFINITY); + } + + @Test + public void testGetBoundariesAfterRemoveBoundaryNode() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + NodeImpl n1 = new NodeImpl("1"); + n1.setPosition(0, 0); + n1.setSize(5); + + NodeImpl n2 = new NodeImpl("2"); + n2.setPosition(100, 200); + n2.setSize(10); // This will be at the boundary + + NodeImpl n3 = new NodeImpl("3"); + n3.setPosition(50, 100); + n3.setSize(8); + + q.addNode(n1); + q.addNode(n2); + q.addNode(n3); + + // Remove the node that was at the max boundary + q.removeNode(n2); + + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, -5f); // n1: 0 - 5 + Assert.assertEquals(boundaries.minY, -5f); // n1: 0 - 5 + Assert.assertEquals(boundaries.maxX, 58f); // n3: 50 + 8 + Assert.assertEquals(boundaries.maxY, 108f); // n3: 100 + 8 + } + + @Test + public void testGetBoundariesAfterUpdate() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + NodeImpl node = new NodeImpl("0"); + node.setPosition(0, 0); + node.setSize(5); + q.addNode(node); + + // Update position + node.setPosition(100, 200); + node.setSize(15); + q.updateNode(node, 85, 185, 115, 215); // 100±15, 200±15 + + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, 85f); + Assert.assertEquals(boundaries.minY, 185f); + Assert.assertEquals(boundaries.maxX, 115f); + Assert.assertEquals(boundaries.maxY, 215f); + } + + @Test + public void testGetBoundariesAfterUpdateBoundaryNode() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + NodeImpl n1 = new NodeImpl("1"); + n1.setPosition(0, 0); + n1.setSize(5); + + NodeImpl n2 = new NodeImpl("2"); + n2.setPosition(100, 200); + n2.setSize(10); + + q.addNode(n1); + q.addNode(n2); + + // Move the boundary node to a smaller position + n2.setPosition(50, 100); + n2.setSize(5); + q.updateNode(n2, 45, 95, 55, 105); + + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, -5f); // n1: 0 - 5 + Assert.assertEquals(boundaries.minY, -5f); // n1: 0 - 5 + Assert.assertEquals(boundaries.maxX, 55f); // n2: 50 + 5 + Assert.assertEquals(boundaries.maxY, 105f); // n2: 100 + 5 + } + + @Test + public void testGetBoundariesWithZeroSizeNodes() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + NodeImpl n1 = new NodeImpl("1"); + n1.setPosition(10, 20); + n1.setSize(0); + + NodeImpl n2 = new NodeImpl("2"); + n2.setPosition(-5, -10); + n2.setSize(0); + + q.addNode(n1); + q.addNode(n2); + + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, -5f); + Assert.assertEquals(boundaries.minY, -10f); + Assert.assertEquals(boundaries.maxX, 10f); + Assert.assertEquals(boundaries.maxY, 20f); + } + + @Test + public void testGetBoundariesWithOutOfBoundsNodes() { + NodesQuadTree q = new NodesQuadTree(new Rect2D(-10, -10, 10, 10)); + + NodeImpl n1 = new NodeImpl("1"); + n1.setPosition(100, 200); // Way out of bounds + n1.setSize(5); + + NodeImpl n2 = new NodeImpl("2"); + n2.setPosition(0, 0); // Within bounds + n2.setSize(2); + + q.addNode(n1); + q.addNode(n2); + + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, -2f); // n2: 0 - 2 + Assert.assertEquals(boundaries.minY, -2f); // n2: 0 - 2 + Assert.assertEquals(boundaries.maxX, 105f); // n1: 100 + 5 + Assert.assertEquals(boundaries.maxY, 205f); // n1: 200 + 5 + } + + @Test + public void testGetBoundariesWithNegativeCoordinates() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + NodeImpl n1 = new NodeImpl("1"); + n1.setPosition(-100, -200); + n1.setSize(10); + + NodeImpl n2 = new NodeImpl("2"); + n2.setPosition(-50, -150); + n2.setSize(5); + + q.addNode(n1); + q.addNode(n2); + + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, -110f); // n1: -100 - 10 + Assert.assertEquals(boundaries.minY, -210f); // n1: -200 - 10 + Assert.assertEquals(boundaries.maxX, -45f); // n2: -50 + 5 + Assert.assertEquals(boundaries.maxY, -145f); // n2: -150 + 5 + } + + @Test + public void testGetBoundariesWithMixedCoordinates() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + NodeImpl n1 = new NodeImpl("1"); + n1.setPosition(-50, 100); + n1.setSize(20); + + NodeImpl n2 = new NodeImpl("2"); + n2.setPosition(75, -80); + n2.setSize(15); + + q.addNode(n1); + q.addNode(n2); + + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, -70f); // n1: -50 - 20 + Assert.assertEquals(boundaries.minY, -95f); // n2: -80 - 15 + Assert.assertEquals(boundaries.maxX, 90f); // n2: 75 + 15 + Assert.assertEquals(boundaries.maxY, 120f); // n1: 100 + 20 + } + + @Test + public void testGetBoundariesSingleNodeAtOrigin() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + NodeImpl node = new NodeImpl("0"); + node.setPosition(0, 0); + node.setSize(1); + + q.addNode(node); + + Rect2D boundaries = q.getBoundaries(); + Assert.assertNotNull(boundaries); + Assert.assertEquals(boundaries.minX, -1f); + Assert.assertEquals(boundaries.minY, -1f); + Assert.assertEquals(boundaries.maxX, 1f); + Assert.assertEquals(boundaries.maxY, 1f); + } + + @Test + public void testGetMaximumObjectsReached() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + addRandomNodes(q, GraphStoreConfiguration.SPATIAL_INDEX_MAX_OBJECTS_PER_NODE, 0); + Assert.assertEquals(q.getObjectCount(), GraphStoreConfiguration.SPATIAL_INDEX_MAX_OBJECTS_PER_NODE); + Assert.assertEquals(q.getNodeCount(true), 1); + Assert.assertEquals(q.getNodeCount(false), 1); + NodeImpl[] newNodes = addRandomNodes(q, 1, GraphStoreConfiguration.SPATIAL_INDEX_MAX_OBJECTS_PER_NODE); + Assert.assertEquals(q.getNodeCount(true), 4); + Assert.assertEquals(q.getObjectCount(), GraphStoreConfiguration.SPATIAL_INDEX_MAX_OBJECTS_PER_NODE + 1); + q.removeNode(newNodes[0]); + Assert.assertEquals(q.getObjectCount(), GraphStoreConfiguration.SPATIAL_INDEX_MAX_OBJECTS_PER_NODE); + } + + @Test + public void testCountsWithLargerDepth() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + int totalNodes = GraphStoreConfiguration.SPATIAL_INDEX_MAX_OBJECTS_PER_NODE * 10; + addRandomNodes(q, totalNodes, 0); + Assert.assertEquals(q.getObjectCount(), totalNodes); + Assert.assertTrue(q.getNodeCount(true) >= 10); + Assert.assertTrue(q.getNodeCount(false) >= 10); + } + + @Test + public void testIteratorWithLargerGraph() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + int totalNodes = 100000; + NodeImpl[] nodes = addRandomNodes(q, totalNodes, 0); + assertSameSet(q.getAllNodes(), nodes); + } + + @Test + public void testGetNodesInArea() { + Rect2D area = new Rect2D(-1000, -1000, 1000, 1000); + NodesQuadTree q = new NodesQuadTree(area); + int totalNodes = 30000; + NodeImpl[] nodes = addRandomNodes(q, totalNodes, 0, area); + Rect2D subarea = new Rect2D(-100, -100, 100, 100); + + assertSameSet(q + .getNodes(subarea, false), Arrays + .stream(nodes).filter(n -> subarea.intersects(n.getSpatialData().minX, n + .getSpatialData().minY, n.getSpatialData().maxX, n.getSpatialData().maxY)) + .toArray(NodeImpl[]::new)); + } + + @Test + public void testGetNodesInAreaApproximate() { + Rect2D area = new Rect2D(-1000, -1000, 1000, 1000); + NodesQuadTree q = new NodesQuadTree(area); + int totalNodes = 30000; + NodeImpl[] nodes = addRandomNodes(q, totalNodes, 0, area); + Rect2D subarea = new Rect2D(-100, -100, -1, -1); + + // Approximate should return all nodes that are in the quadtree nodes + // intersecting the area + assertSameSet(q.getNodes(subarea, true), Arrays.stream(nodes) + .filter(n -> subarea.intersects(n.getSpatialData().quadTreeNode.quadRect())).toArray(NodeImpl[]::new)); + } + + @Test + public void testGetNodesInAreaWithPredicate() { + NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); + + Rect2D rect = new Rect2D(-10, -10, 10, 10); + NodeImpl[] nodes = addRandomNodes(q, 2, 0, rect); + NodeImpl node1 = nodes[0]; + + assertSameSet(q.getNodes(rect, false, n -> n == node1), node1); + } + + @Test + public void testGetAllEdges() { + GraphStore store = GraphGenerator.generateEmptyGraphStore(getConfig()); + NodesQuadTree q = store.spatialIndex.nodesTree; + NodeImpl[] nodes = addRandomNodes(q, 2, 0); + EdgeImpl[] edges = addRandomEdges(store, nodes, 10); + + assertSameSet(q.getEdges(), edges); + } + + @Test + public void testGetAllEdgesLarge() { + GraphStore store = GraphGenerator.generateEmptyGraphStore(getConfig()); + NodesQuadTree q = store.spatialIndex.nodesTree; + NodeImpl[] nodes = addRandomNodes(q, 10000, 0); + EdgeImpl[] edges = addRandomEdges(store, nodes, 100000); + + assertSameSet(q.getEdges(), edges); + } + + @Test + public void testGetEdgesInArea() { + Rect2D area = new Rect2D(-1000, -1000, 1000, 1000); + GraphStore store = GraphGenerator.generateEmptyGraphStore(getConfig()); + NodesQuadTree q = store.spatialIndex.nodesTree; + NodeImpl[] nodes = addRandomNodes(q, 30000, 0, area); + EdgeImpl[] edges = addRandomEdges(store, nodes, 100000); + + Rect2D subarea = new Rect2D(-100, -100, 100, 100); + + assertSameSet(q.getEdges(subarea, false), Arrays.stream(edges) + .filter(e -> edgeIntersectsArea(e, subarea, false)).toArray(EdgeImpl[]::new)); + } + + @Test + public void testGetEdgesInAreaGlobal() { + Rect2D area = new Rect2D(-1000, -1000, 1000, 1000); + GraphStore store = GraphGenerator.generateEmptyGraphStore(getConfig()); + NodesQuadTree q = store.spatialIndex.nodesTree; + NodeImpl[] nodes = addRandomNodes(q, 30000, 0, area); + EdgeImpl[] edges = addRandomEdges(store, nodes, 100000); + + Rect2D subarea = new Rect2D(-600, -600, 600, 600); + + assertSameSet(q.getEdges(subarea, false), Arrays.stream(edges) + .filter(e -> edgeIntersectsArea(e, subarea, false)).toArray(EdgeImpl[]::new)); + } + + @Test + public void testGetEdgesInAreaApproximate() { + Rect2D area = new Rect2D(-1000, -1000, 1000, 1000); + GraphStore store = GraphGenerator.generateEmptyGraphStore(getConfig()); + NodesQuadTree q = store.spatialIndex.nodesTree; + NodeImpl[] nodes = addRandomNodes(q, 30000, 0, area); + EdgeImpl[] edges = addRandomEdges(store, nodes, 100000); + + Rect2D subarea = new Rect2D(-100, -100, -1, -1); + + assertSameSet(q.getEdges(subarea, true), Arrays.stream(edges).filter(e -> edgeIntersectsArea(e, subarea, true)) + .toArray(EdgeImpl[]::new)); + } + + @Test + public void testGetEdgesInAreaWithPredicate() { + GraphStore store = GraphGenerator.generateEmptyGraphStore(getConfig()); + NodesQuadTree q = store.spatialIndex.nodesTree; + + Rect2D rect = new Rect2D(-10, -10, 10, 10); + NodeImpl[] nodes = addRandomNodes(q, 2, 0, rect); + EdgeImpl[] edges = addRandomEdges(store, nodes, 2); + EdgeImpl edge1 = edges[0]; + + assertSameSet(q.getEdges(rect, false), edges); + assertSameSet(q.getEdges(rect, false, e -> e == edge1), edge1); + assertSameSet(q.getEdges(rect, true), edges); + assertSameSet(q.getEdges(rect, true, e -> e == edge1), edge1); + } + + @Test + public void testGetEdgesInAreaBidirectional() { + Rect2D rect = new Rect2D(100, 100, 100, 100); + GraphStore store = GraphGenerator.generateEmptyGraphStore(getConfig()); + NodeImpl[] nodes = GraphGenerator.generateNodeList(10000, store, rect); + store.addAllNodes(Arrays.asList(nodes)); + NodesQuadTree q = store.spatialIndex.nodesTree; + + nodes[0].setPosition(-1000, -1000); + EdgeImpl[] edges = addRandomEdges(store, new NodeImpl[] { nodes[0], nodes[1] }, 1); + + // Edge should be returned once, as only one node is in the area + assertSameSetAndCount(q.getEdges(new Rect2D(-2000, -2000, -999, -999), false), edges); + + nodes[1].setPosition(-1000, -1000); + + // Edge should be returned twice, once for each node + assertSameSetAndCount(q + .getEdges(new Rect2D(-2000, -2000, -999, -999), false), new EdgeImpl[] { edges[0], edges[0] }); + } + + // Utils + + private void assertSameSet(NodeIterable iterable, Node... expected) { + Assert.assertTrue(expected.length > 0, "Expected array must not be empty"); + ObjectSet set = new ObjectOpenHashSet<>(expected.length); + set.addAll(Arrays.asList(expected)); + Assert.assertEquals(iterable.toSet(), set); + Assert.assertEquals(iterable.stream().collect(Collectors.toSet()), set); + Assert.assertEquals(iterable.parallelStream().collect(Collectors.toSet()), set); + } + + private void assertSameSetAndCount(NodeIterable iterable, Node... expected) { + assertSameSet(iterable, expected); + Assert.assertEquals(iterable.toCollection().size(), expected.length); + Assert.assertEquals(iterable.stream().count(), expected.length); + Assert.assertEquals(iterable.parallelStream().count(), expected.length); + } + + private void assertSameSet(EdgeIterable iterable, Edge... expected) { + Assert.assertTrue(expected.length > 0, "Expected array must not be empty"); + ObjectSet set = new ObjectOpenHashSet<>(expected.length); + set.addAll(Arrays.asList(expected)); + Assert.assertEquals(iterable.toSet(), set); + Assert.assertEquals(iterable.stream().collect(Collectors.toSet()), set); + Assert.assertEquals(iterable.parallelStream().collect(Collectors.toSet()), set); + } + + private void assertSameSetAndCount(EdgeIterable iterable, Edge... expected) { + assertSameSet(iterable, expected); + Assert.assertEquals(iterable.toCollection().size(), expected.length); + Assert.assertEquals(iterable.stream().count(), expected.length); + Assert.assertEquals(iterable.parallelStream().count(), expected.length); + } + + private void assertEmpty(NodeIterable iterable) { + Assert.assertEquals(iterable.toCollection().size(), 0); + } + + private NodeImpl[] addRandomNodes(GraphStore store, int count, int startIndex) { + return addRandomNodes(store, count, startIndex, BOUNDS_RECT); + } + + private NodeImpl[] addRandomNodes(GraphStore store, int count, int startIndex, Rect2D area) { + NodeImpl[] nodes = generateNodes(count, startIndex, area); + for (NodeImpl n : nodes) { + store.addNode(n); + } + return nodes; + } + + private NodeImpl[] addRandomNodes(NodesQuadTree q, int count, int startIndex) { + return addRandomNodes(q, count, startIndex, BOUNDS_RECT); + } + + private NodeImpl[] addRandomNodes(NodesQuadTree q, int count, int startIndex, Rect2D area) { + NodeImpl[] nodes = generateNodes(count, startIndex, area); + for (NodeImpl n : nodes) { + q.addNode(n); + } + return nodes; + } + + private NodeImpl[] generateNodes(int count, int startIndex, Rect2D area) { + Random rand = new Random(42L); + NodeImpl[] nodes = new NodeImpl[count]; + for (int i = 0; i < count; i++) { + NodeImpl node = new NodeImpl(String.valueOf(startIndex++)); + float x = area.minX + rand.nextFloat() * (area.maxX - area.minX); + float y = area.minY + rand.nextFloat() * (area.maxY - area.minY); + node.setPosition(x, y); + node.setSize(1.0f); + nodes[i] = node; + } + return nodes; + } + + private EdgeImpl[] addRandomEdges(GraphStore store, NodeImpl[] nodes, int count) { + Random rand = new Random(789012L); + for (NodeImpl n : nodes) { + store.addNode(n); + } + EdgeImpl[] edges = new EdgeImpl[count]; + int edgeIndex = 0; + while (edgeIndex < count) { + NodeImpl source = nodes[rand.nextInt(nodes.length)]; + NodeImpl target = nodes[rand.nextInt(nodes.length)]; + if (source != target) { + EdgeImpl edge = new EdgeImpl(String.valueOf(edgeIndex), store, source, target, 0, 1.0, true); + edges[edgeIndex] = edge; + store.addEdge(edge); + edgeIndex++; + } + } + return edges; + } + + private Configuration getConfig() { + return Configuration.builder().enableSpatialIndex(true).build(); + } + + private boolean edgeIntersectsArea(EdgeImpl e, Rect2D area, boolean approximate) { + if (approximate) { + return area.intersects(e.source.getSpatialData().quadTreeNode.quadRect()) || area + .intersects(e.target.getSpatialData().quadTreeNode.quadRect()); + } + return area.intersects(e.source.getSpatialData().minX, e.source.getSpatialData().minY, e.source + .getSpatialData().maxX, e.source.getSpatialData().maxY) || area + .intersects(e.target.getSpatialData().minX, e.target.getSpatialData().minY, e.target + .getSpatialData().maxX, e.target.getSpatialData().maxY); + } +} diff --git a/store/src/test/java/org/gephi/graph/impl/NumberGenerator.java b/src/test/java/org/gephi/graph/impl/NumberGenerator.java similarity index 100% rename from store/src/test/java/org/gephi/graph/impl/NumberGenerator.java rename to src/test/java/org/gephi/graph/impl/NumberGenerator.java diff --git a/src/test/java/org/gephi/graph/impl/SerializationCompatibilityTest.java b/src/test/java/org/gephi/graph/impl/SerializationCompatibilityTest.java new file mode 100644 index 00000000..2ef63ad7 --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/SerializationCompatibilityTest.java @@ -0,0 +1,613 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Instant; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.gephi.graph.api.Configuration; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Graph; +import org.gephi.graph.api.GraphModel; +import org.gephi.graph.api.GraphView; +import org.gephi.graph.api.Interval; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.TimeFormat; +import org.gephi.graph.api.TimeRepresentation; +import org.gephi.graph.api.types.IntervalBooleanMap; +import org.gephi.graph.api.types.IntervalCharMap; +import org.gephi.graph.api.types.IntervalDoubleMap; +import org.gephi.graph.api.types.IntervalIntegerMap; +import org.gephi.graph.api.types.IntervalSet; +import org.gephi.graph.api.types.IntervalStringMap; +import org.gephi.graph.api.types.TimestampBooleanMap; +import org.gephi.graph.api.types.TimestampCharMap; +import org.gephi.graph.api.types.TimestampDoubleMap; +import org.gephi.graph.api.types.TimestampIntegerMap; +import org.gephi.graph.api.types.TimestampSet; +import org.gephi.graph.api.types.TimestampStringMap; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Golden-fixture regression suite for the serialization format. + *

+ * Enforces three contracts: + *

    + *
  1. Backward compatibility - every fixture, from the oldest minor to the current one, deserializes and yields + * the expected content.
  2. + *
  3. Format drift - for the current minor, serializing the model built by {@link SerializationFixtureGenerator} + * produces bytes identical to the committed file. Older minors are exempt, since graphstore no longer writes those + * formats.
  4. + *
  5. Determinism - the same content serializes to identical bytes regardless of build order.
  6. + *
+ * + * See src/test/resources/serialization/README.md before changing any fixture file. + */ +public class SerializationCompatibilityTest { + + private static final String RESOURCE_ROOT = "/serialization"; + private static final String CURRENT_MINOR = SerializationFixtureGenerator.CURRENT_MINOR; + private static final String[] ALL_MINORS = { "0.4", "0.5", "0.6", "0.7", CURRENT_MINOR }; + + // Column counts of a default model, as asserted on the legacy fixtures + private static final int DEFAULT_NODE_COLUMNS = 3; + private static final int DEFAULT_EDGE_COLUMNS = 4; + + // Columns added by SerializationFixtureGenerator on the type-surface fixtures + private static final int GENERATED_STATIC_COLUMNS = 25; + private static final int GENERATED_DYNAMIC_COLUMNS = 10; + private static final int GENERATED_COLLECTION_COLUMNS = 3; + + // Contract 1: every minor deserializes and holds the expected content + + @Test(dataProvider = "allFixtures") + public void testFixtureDeserializes(String minor, String fixture) throws IOException { + byte[] committed = readFixture(minor, fixture); + Assert.assertTrue(committed.length > 0, "Fixture " + path(minor, fixture) + " is empty"); + + GraphModel graphModel = deserialize(committed); + Assert.assertNotNull(graphModel, "Deserialization of " + path(minor, fixture) + " returned null"); + assertContent(minor, fixture, graphModel); + } + + // Contract 2: the current minor is byte-pinned + // + // Compares today's write path against the committed golden file. Reading a fixture back and re-serializing it + // would not work, because deserialization is not byte-idempotent here: GraphVersion counters are restored from the + // stream and then incremented again as elements are re-inserted, TextProperties width/height are dropped on read, + // and the time index is rebuilt from the elements rather than restored. Contract 1 covers the read path. + + @Test(dataProvider = "currentMinorFixtures") + public void testCurrentMinorIsByteIdentical(String fixture) throws IOException { + byte[] committed = readFixture(CURRENT_MINOR, fixture); + byte[] regenerated = SerializationFixtureGenerator.serialize(buildFixtureModel(fixture)); + + assertBytesEqual(committed, regenerated, "Serialization format drift for fixture " + path(CURRENT_MINOR, fixture) + ".\nThe model built by SerializationFixtureGenerator no longer serializes to the committed bytes.\nThis is either a bug or an intended format change; see src/test/resources/serialization/README.md."); + } + + // Contract 3: determinism + + @Test(dataProvider = "currentMinorFixtures") + public void testSerializationIsDeterministic(String fixture) throws IOException { + GraphModel graphModel = deserialize(readFixture(CURRENT_MINOR, fixture)); + + byte[] first = SerializationFixtureGenerator.serialize(graphModel); + byte[] second = SerializationFixtureGenerator.serialize(graphModel); + + assertBytesEqual(first, second, "Serializing the same model twice produced different bytes for fixture " + path(CURRENT_MINOR, fixture) + ".\nSomething in the write path depends on iteration order or identity" + " hashing rather than on content."); + } + + @Test(dataProvider = "currentMinorFixtures") + public void testFreshlyBuiltModelsSerializeDeterministically(String fixture) throws IOException { + byte[] first = SerializationFixtureGenerator.serialize(buildFixtureModel(fixture)); + byte[] second = SerializationFixtureGenerator.serialize(buildFixtureModel(fixture)); + + assertBytesEqual(first, second, "Building the model for fixture '" + fixture + "' twice and serializing produced different bytes."); + } + + @Test + public void testGraphAttributesOrderIsCanonical() throws IOException { + // The same graph attributes inserted in opposite orders must serialize identically. See GraphAttributesImpl. + String[] keys = { "zulu", "alpha", "mike", "bravo", "yankee", "charlie", "november", "delta" }; + + GraphModel forward = GraphModel.Factory.newInstance(); + for (int i = 0; i < keys.length; i++) { + forward.getGraph().setAttribute(keys[i], "value-" + i); + } + + GraphModel backward = GraphModel.Factory.newInstance(); + for (int i = keys.length - 1; i >= 0; i--) { + backward.getGraph().setAttribute(keys[i], "value-" + i); + } + + assertBytesEqual(SerializationFixtureGenerator.serialize(forward), SerializationFixtureGenerator + .serialize(backward), "Graph attributes are not serialized in a canonical order: the same attributes" + " inserted in a different order produced different bytes."); + } + + // Round-trip only, no byte assertions: the hash-ordered surface excluded from the fixtures + + @Test + public void testRoundTripHashOrderedAttributes() throws IOException { + GraphModel graphModel = GraphModel.Factory.newInstance(); + graphModel.getNodeTable().addColumn("c_list", List.class); + graphModel.getNodeTable().addColumn("c_set", Set.class); + graphModel.getNodeTable().addColumn("c_map", Map.class); + + Graph graph = graphModel.getGraph(); + Node node = graphModel.factory().newNode("n1"); + graph.addNode(node); + + List list = new ArrayList<>(Arrays.asList("a", "b", "c")); + Set set = new HashSet<>(Arrays.asList("x", "y", "z")); + Map map = new HashMap<>(); + map.put("k1", "v1"); + map.put("k2", "v2"); + map.put("k3", "v3"); + + node.setAttribute("c_list", list); + node.setAttribute("c_set", set); + node.setAttribute("c_map", map); + + // Many graph attribute keys, of assorted types + for (int i = 0; i < 50; i++) { + graph.setAttribute("key-" + i, "value-" + i); + } + graph.setAttribute("an-int", 7); + graph.setAttribute("a-char", 'q'); + graph.setAttribute("an-array", new int[] { 1, 2, 3 }); + + GraphModel read = deserialize(SerializationFixtureGenerator.serialize(graphModel)); + Graph readGraph = read.getGraph(); + Node readNode = readGraph.getNode("n1"); + Assert.assertNotNull(readNode); + + Assert.assertEquals(new ArrayList<>((List) readNode.getAttribute("c_list")), list); + Assert.assertEquals(new HashSet<>((Set) readNode.getAttribute("c_set")), set); + Assert.assertEquals(new HashMap<>((Map) readNode.getAttribute("c_map")), map); + + Assert.assertEquals(readGraph.getAttributeKeys().size(), 53); + for (int i = 0; i < 50; i++) { + Assert.assertEquals(readGraph.getAttribute("key-" + i), "value-" + i); + } + Assert.assertEquals(readGraph.getAttribute("an-int"), 7); + Assert.assertEquals(readGraph.getAttribute("a-char"), 'q'); + Assert.assertEquals((int[]) readGraph.getAttribute("an-array"), new int[] { 1, 2, 3 }); + } + + // Data providers + + @DataProvider(name = "allFixtures") + public Object[][] allFixtures() { + List rows = new ArrayList<>(); + for (String minor : ALL_MINORS) { + for (String fixture : fixturesFor(minor)) { + rows.add(new Object[] { minor, fixture }); + } + } + return rows.toArray(new Object[0][]); + } + + @DataProvider(name = "currentMinorFixtures") + public Object[][] currentMinorFixtures() { + String[] fixtures = fixturesFor(CURRENT_MINOR); + Object[][] rows = new Object[fixtures.length][]; + for (int i = 0; i < fixtures.length; i++) { + rows[i] = new Object[] { fixtures[i] }; + } + return rows; + } + + private static String[] fixturesFor(String minor) { + if (CURRENT_MINOR.equals(minor)) { + return new String[] { SerializationFixtureGenerator.BASIC, SerializationFixtureGenerator.PARALLEL, SerializationFixtureGenerator.TYPES_TIMESTAMP, SerializationFixtureGenerator.TYPES_INTERVAL, SerializationFixtureGenerator.VIEWS }; + } + // The legacy fixtures only cover the format skeleton + return new String[] { SerializationFixtureGenerator.BASIC, SerializationFixtureGenerator.PARALLEL }; + } + + // Content assertions + + private void assertContent(String minor, String fixture, GraphModel graphModel) { + switch (fixture) { + case SerializationFixtureGenerator.BASIC: + assertBasic(graphModel); + break; + case SerializationFixtureGenerator.PARALLEL: + assertParallel(graphModel); + break; + case SerializationFixtureGenerator.TYPES_TIMESTAMP: + assertTypeSurface(graphModel, TimeRepresentation.TIMESTAMP); + break; + case SerializationFixtureGenerator.TYPES_INTERVAL: + assertTypeSurface(graphModel, TimeRepresentation.INTERVAL); + break; + case SerializationFixtureGenerator.VIEWS: + assertViews(graphModel); + break; + default: + Assert.fail("No content assertions defined for fixture " + path(minor, fixture)); + } + } + + private void assertBasic(GraphModel graphModel) { + Graph graph = graphModel.getGraph(); + Assert.assertEquals(graph.getNodeCount(), 2); + Assert.assertEquals(graph.getEdgeCount(), 1); + Assert.assertEquals(graphModel.getNodeTable().countColumns(), DEFAULT_NODE_COLUMNS); + Assert.assertEquals(graphModel.getEdgeTable().countColumns(), DEFAULT_EDGE_COLUMNS); + + Node node1 = graph.getNode(SerializationFixtureGenerator.NODE_ID_1); + Node node2 = graph.getNode(SerializationFixtureGenerator.NODE_ID_2); + Assert.assertNotNull(node1); + Assert.assertNotNull(node2); + Assert.assertEquals(node1.getLabel(), SerializationFixtureGenerator.NODE_LABEL_1); + Assert.assertEquals(node2.getLabel(), SerializationFixtureGenerator.NODE_LABEL_2); + Assert.assertEquals(node1.x(), 10.0f); + Assert.assertEquals(node1.y(), 10.0f); + Assert.assertEquals(node1.z(), 1.0f); + Assert.assertEquals(node1.size(), 11.0f); + + Edge edge = graph.getEdge(SerializationFixtureGenerator.EDGE_ID_1); + Assert.assertNotNull(edge); + Assert.assertEquals(edge.getSource(), node1); + Assert.assertEquals(edge.getTarget(), node2); + Assert.assertTrue(edge.isDirected()); + Assert.assertEquals(edge.getWeight(), 1.0); + } + + private void assertParallel(GraphModel graphModel) { + Graph graph = graphModel.getGraph(); + Assert.assertEquals(graph.getNodeCount(), 2); + Assert.assertEquals(graph.getEdgeCount(), 2); + Assert.assertEquals(graphModel.getNodeTable().countColumns(), DEFAULT_NODE_COLUMNS); + Assert.assertEquals(graphModel.getEdgeTable().countColumns(), DEFAULT_EDGE_COLUMNS); + + Edge edge1 = graph.getEdge(SerializationFixtureGenerator.EDGE_ID_1); + Edge edge2 = graph.getEdge(SerializationFixtureGenerator.EDGE_ID_2); + Assert.assertNotNull(edge1); + Assert.assertNotNull(edge2); + Assert.assertNotEquals(edge1.getType(), edge2.getType()); + Assert.assertEquals(edge1.getTypeLabel(), SerializationFixtureGenerator.EDGE_TYPE_1); + Assert.assertEquals(edge2.getTypeLabel(), SerializationFixtureGenerator.EDGE_TYPE_2); + } + + private void assertTypeSurface(GraphModel graphModel, TimeRepresentation timeRepresentation) { + boolean interval = timeRepresentation == TimeRepresentation.INTERVAL; + + Configuration config = graphModel.getConfiguration(); + Assert.assertEquals(config.getTimeRepresentation(), timeRepresentation); + Assert.assertEquals(config.getEdgeWeightType(), interval ? IntervalDoubleMap.class : TimestampDoubleMap.class); + Assert.assertEquals(graphModel.getTimeFormat(), interval ? TimeFormat.DATETIME : TimeFormat.DOUBLE); + Assert.assertEquals(graphModel.getTimeZone(), ZoneId.of("Europe/Paris")); + + Assert.assertEquals(graphModel.getNodeTable() + .countColumns(), DEFAULT_NODE_COLUMNS + GENERATED_STATIC_COLUMNS + GENERATED_DYNAMIC_COLUMNS + GENERATED_COLLECTION_COLUMNS); + Assert.assertEquals(graphModel.getEdgeTable() + .countColumns(), DEFAULT_EDGE_COLUMNS + GENERATED_STATIC_COLUMNS + GENERATED_DYNAMIC_COLUMNS); + + // Column types survived the round trip, including the boxed-array standardization + Assert.assertEquals(graphModel.getNodeTable().getColumn("t_character").getTypeClass(), Character.class); + Assert.assertEquals(graphModel.getNodeTable().getColumn("t_char_array").getTypeClass(), char[].class); + Assert.assertEquals(graphModel.getNodeTable().getColumn("t_boxed_boolean_array") + .getTypeClass(), boolean[].class); + Assert.assertEquals(graphModel.getNodeTable().getColumn("t_boxed_character_array") + .getTypeClass(), char[].class); + Assert.assertEquals(graphModel.getNodeTable().getColumn("t_set").getTypeClass(), Set.class); + Assert.assertEquals(graphModel.getNodeTable().getColumn("t_map").getTypeClass(), Map.class); + + Graph graph = graphModel.getGraph(); + Assert.assertEquals(graph.getNodeCount(), 2); + Assert.assertEquals(graph.getEdgeCount(), 1); + + Node node1 = graph.getNode(SerializationFixtureGenerator.NODE_ID_1); + Assert.assertNotNull(node1); + assertStaticValues(node1); + assertDynamicValues(node1, timeRepresentation); + + // Collections: only the list carries a value, see SerializationFixtureGenerator + Assert.assertEquals(new ArrayList<>((List) node1.getAttribute("t_list")), Arrays.asList("first", "second")); + Assert.assertNull(node1.getAttribute("t_set")); + Assert.assertNull(node1.getAttribute("t_map")); + + // Element and text properties + Assert.assertEquals(node1.x(), 1.5f); + Assert.assertEquals(node1.y(), -2.5f); + Assert.assertEquals(node1.z(), 3.5f); + Assert.assertEquals(node1.size(), 7.25f); + Assert.assertEquals(node1.alpha(), 0.75f, 0.005f); + Assert.assertEquals(node1.getTextProperties().getText(), "node one text"); + Assert.assertEquals(node1.getTextProperties().getSize(), 13.5f); + Assert.assertFalse(node1.getTextProperties().isVisible()); + // The fixture was written with text dimensions 42x24 and the bytes carry them, but NodeImpl.setTextProperties + // does not copy width and height back, so they are lost on read. Pinned here as the current behaviour. + Assert.assertEquals(node1.getTextProperties().getWidth(), 0.0f); + Assert.assertEquals(node1.getTextProperties().getHeight(), 0.0f); + + // Element time set + Node node2 = graph.getNode(SerializationFixtureGenerator.NODE_ID_2); + Assert.assertNotNull(node2); + if (interval) { + Assert.assertEquals(node1.getIntervals(), new Interval[] { new Interval(1.0, 5.0) }); + Assert.assertEquals(node2.getIntervals(), new Interval[] { new Interval(2.0, 3.0) }); + } else { + Assert.assertEquals(node1.getTimestamps(), new double[] { 1.0, 4.0 }); + Assert.assertEquals(node2.getTimestamps(), new double[] { 2.0 }); + } + + // Edge, edge type and dynamic weight + Edge edge1 = graph.getEdge(SerializationFixtureGenerator.EDGE_ID_1); + Assert.assertNotNull(edge1); + Assert.assertEquals(edge1.getTypeLabel(), SerializationFixtureGenerator.EDGE_TYPE_1); + Assert.assertTrue(edge1.hasDynamicWeight()); + Assert.assertEquals(edge1.getTextProperties().getText(), "edge one text"); + assertStaticValues(edge1); + assertDynamicValues(edge1, timeRepresentation); + if (interval) { + Assert.assertEquals(edge1.getWeight(new Interval(1.0, 2.0)), 2.5); + Assert.assertEquals(edge1.getWeight(new Interval(3.0, 4.0)), 3.5); + } else { + Assert.assertEquals(edge1.getWeight(1.0), 2.5); + Assert.assertEquals(edge1.getWeight(3.0), 3.5); + } + + // Graph attributes + Assert.assertEquals(graph.getAttribute("attr-string"), "graph level"); + Assert.assertEquals(graph.getAttribute("attr-int"), 42); + Assert.assertEquals(graph.getAttribute("attr-double"), 3.5); + Assert.assertEquals(graph.getAttribute("attr-char"), 'g'); + Assert.assertEquals((char[]) graph.getAttribute("attr-char-array"), new char[] { 'a', 'b' }); + Assert.assertEquals(graph.getAttribute("attr-instant"), Instant.ofEpochSecond(1_600_000_000L, 123)); + if (interval) { + Assert.assertEquals(graph.getAttribute("attr-dynamic", new Interval(1.0, 2.0)), "first"); + Assert.assertEquals(graph.getAttribute("attr-dynamic", new Interval(3.0, 4.0)), "second"); + } else { + Assert.assertEquals(graph.getAttribute("attr-dynamic", 1.0), "first"); + Assert.assertEquals(graph.getAttribute("attr-dynamic", 3.0), "second"); + } + } + + private void assertStaticValues(org.gephi.graph.api.Element element) { + Assert.assertEquals(element.getAttribute("t_boolean"), Boolean.TRUE); + Assert.assertEquals(element.getAttribute("t_integer"), 123456); + Assert.assertEquals(element.getAttribute("t_short"), (short) -12); + Assert.assertEquals(element.getAttribute("t_long"), 9876543210L); + Assert.assertEquals(element.getAttribute("t_biginteger"), new BigInteger("123456789012345678901234567890")); + Assert.assertEquals(element.getAttribute("t_byte"), (byte) 7); + Assert.assertEquals(element.getAttribute("t_float"), 1.25f); + Assert.assertEquals(element.getAttribute("t_double"), -2.5); + Assert.assertEquals(element.getAttribute("t_bigdecimal"), new BigDecimal("1234567890.0987654321")); + Assert.assertEquals(element.getAttribute("t_character"), 'Z'); + Assert.assertEquals(element.getAttribute("t_string"), "hello é中文"); + Assert.assertEquals(element.getAttribute("t_instant"), Instant.ofEpochSecond(1_500_000_000L, 42)); + + Assert.assertEquals((boolean[]) element.getAttribute("t_boolean_array"), new boolean[] { true, false, true }); + Assert.assertEquals((int[]) element.getAttribute("t_int_array"), new int[] { 1, -2, 3 }); + Assert.assertEquals((short[]) element.getAttribute("t_short_array"), new short[] { 4, -5 }); + Assert.assertEquals((long[]) element.getAttribute("t_long_array"), new long[] { 6L, -7L }); + // BigInteger[] and BigDecimal[] go through the generic ARRAY_OBJECT path and come back as Object[]. The + // element values survive, the array component type does not. + Assert.assertEquals((Object[]) element + .getAttribute("t_biginteger_array"), new Object[] { BigInteger.ONE, new BigInteger("-99") }); + Assert.assertEquals((byte[]) element.getAttribute("t_byte_array"), new byte[] { 8, -9 }); + Assert.assertEquals((float[]) element.getAttribute("t_float_array"), new float[] { 1.5f, -2.5f }); + Assert.assertEquals((double[]) element.getAttribute("t_double_array"), new double[] { 3.5, -4.5 }); + Assert.assertEquals((Object[]) element + .getAttribute("t_bigdecimal_array"), new Object[] { BigDecimal.ONE, new BigDecimal("-0.5") }); + Assert.assertEquals((char[]) element.getAttribute("t_char_array"), new char[] { 'a', 'é', '中' }); + Assert.assertEquals((String[]) element.getAttribute("t_string_array"), new String[] { "one", "two", "three" }); + + Assert.assertEquals((boolean[]) element.getAttribute("t_boxed_boolean_array"), new boolean[] { false, true }); + Assert.assertEquals((char[]) element.getAttribute("t_boxed_character_array"), new char[] { 'x', 'y' }); + } + + private void assertDynamicValues(org.gephi.graph.api.Element element, TimeRepresentation timeRepresentation) { + if (timeRepresentation == TimeRepresentation.INTERVAL) { + IntervalSet set = (IntervalSet) element.getAttribute("t_interval_set"); + Assert.assertNotNull(set); + Assert.assertEquals(set.size(), 2); + Assert.assertTrue(set.contains(new Interval(1.0, 2.0))); + Assert.assertTrue(set.contains(new Interval(3.0, 4.0))); + + Assert.assertEquals(((IntervalBooleanMap) element.getAttribute("t_interval_boolean")) + .getBoolean(new Interval(1.0, 2.0)), true); + Assert.assertEquals(((IntervalIntegerMap) element.getAttribute("t_interval_integer")) + .getInteger(new Interval(3.0, 4.0)), -20); + Assert.assertEquals(((IntervalCharMap) element.getAttribute("t_interval_char")) + .getCharacter(new Interval(3.0, 4.0)), '中'); + Assert.assertEquals(((IntervalStringMap) element.getAttribute("t_interval_string")) + .get(new Interval(1.0, 2.0), (String) null), "alpha"); + Assert.assertNotNull(element.getAttribute("t_interval_short")); + Assert.assertNotNull(element.getAttribute("t_interval_long")); + Assert.assertNotNull(element.getAttribute("t_interval_byte")); + Assert.assertNotNull(element.getAttribute("t_interval_float")); + Assert.assertNotNull(element.getAttribute("t_interval_double")); + } else { + TimestampSet set = (TimestampSet) element.getAttribute("t_timestamp_set"); + Assert.assertNotNull(set); + Assert.assertEquals(set.toPrimitiveArray(), new double[] { 1.0, 3.0 }); + + Assert.assertEquals(((TimestampBooleanMap) element.getAttribute("t_timestamp_boolean")) + .getBoolean(1.0), true); + Assert.assertEquals(((TimestampIntegerMap) element.getAttribute("t_timestamp_integer")) + .getInteger(3.0), -20); + Assert.assertEquals(((TimestampCharMap) element.getAttribute("t_timestamp_char")).getCharacter(3.0), '中'); + Assert.assertEquals(((TimestampStringMap) element.getAttribute("t_timestamp_string")) + .get(1.0, (String) null), "alpha"); + Assert.assertNotNull(element.getAttribute("t_timestamp_short")); + Assert.assertNotNull(element.getAttribute("t_timestamp_long")); + Assert.assertNotNull(element.getAttribute("t_timestamp_byte")); + Assert.assertNotNull(element.getAttribute("t_timestamp_float")); + Assert.assertNotNull(element.getAttribute("t_timestamp_double")); + } + } + + private void assertViews(GraphModel graphModel) { + Graph graph = graphModel.getGraph(); + Assert.assertEquals(graph.getNodeCount(), 5); + Assert.assertEquals(graph.getEdgeCount(), 5); + + // Default type plus the two registered labels + Assert.assertEquals(graphModel.getEdgeTypeCount(), 3); + Assert.assertTrue(graphModel.isMultiGraph()); + Assert.assertTrue(graphModel.isMixed()); + + Assert.assertTrue(graph.getEdge("e0").isDirected()); + Assert.assertFalse(graph.getEdge("e2").isDirected()); + Assert.assertTrue(graph.getEdge("e4").isSelfLoop()); + Assert.assertEquals(graph.getEdge("e3").getWeight(), 4.0); + + Assert.assertEquals(((GraphModelImpl) graphModel).store.viewStore.length, 2); + + GraphViewImpl nodeAndEdgeView = null; + GraphViewImpl nodeOnlyView = null; + for (GraphViewImpl view : ((GraphModelImpl) graphModel).store.viewStore.views) { + if (view == null) { + continue; + } + if (view.isEdgeView()) { + nodeAndEdgeView = view; + } else { + nodeOnlyView = view; + } + } + + Assert.assertNotNull(nodeAndEdgeView, "The node and edge view is missing"); + Assert.assertTrue(nodeAndEdgeView.isNodeView()); + Graph subGraph = graphModel.getGraph(nodeAndEdgeView); + Assert.assertEquals(subGraph.getNodeCount(), 3); + Assert.assertEquals(subGraph.getEdgeCount(), 1); + Assert.assertEquals(subGraph.getAttribute("view-attr"), "on the view"); + Assert.assertEquals(subGraph.getAttribute("view-count"), 3); + + // The other view is node-only and holds the two remaining nodes + Assert.assertNotNull(nodeOnlyView, "The node-only view is missing"); + Assert.assertEquals(graphModel.getGraph(nodeOnlyView).getNodeCount(), 2); + + // GraphViewStore.visibleView is not part of the serialized format, so it always comes back as the main view + Assert.assertTrue(graphModel.getVisibleView().isMainView()); + + Assert.assertEquals(graph.getAttribute("graph-attr"), "main graph"); + Assert.assertEquals(graph.getNode("n2").getAttribute("weight"), 2.0); + Assert.assertEquals(graph.getNode("n2").getAttribute("tag"), "tag2"); + } + + // Helpers + + private static String path(String minor, String fixture) { + return RESOURCE_ROOT + "/" + minor + "/" + fixture + SerializationFixtureGenerator.FILE_EXTENSION; + } + + private static byte[] readFixture(String minor, String fixture) throws IOException { + String resource = path(minor, fixture); + try (InputStream is = SerializationCompatibilityTest.class.getResourceAsStream(resource)) { + Assert.assertNotNull(is, "Missing fixture resource " + resource); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + int read; + while ((read = is.read(buffer)) != -1) { + baos.write(buffer, 0, read); + } + return baos.toByteArray(); + } + } + + private static GraphModel buildFixtureModel(String fixture) { + GraphModel model = SerializationFixtureGenerator.buildAll().get(fixture); + Assert.assertNotNull(model, "SerializationFixtureGenerator does not build fixture '" + fixture + "'"); + return model; + } + + private static GraphModel deserialize(byte[] bytes) throws IOException { + try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes))) { + return GraphModel.Serialization.read(dis); + } + } + + /** + * Byte comparison that reports where the two streams diverge, not just that they do. + * + * @param expected expected bytes + * @param actual actual bytes + * @param context message explaining what the mismatch means + */ + private static void assertBytesEqual(byte[] expected, byte[] actual, String context) { + if (Arrays.equals(expected, actual)) { + return; + } + + int common = Math.min(expected.length, actual.length); + int offset = -1; + for (int i = 0; i < common; i++) { + if (expected[i] != actual[i]) { + offset = i; + break; + } + } + + StringBuilder sb = new StringBuilder(context); + sb.append("\n\nExpected ").append(expected.length).append(" bytes, got ").append(actual.length) + .append(" bytes."); + if (offset < 0) { + offset = common; + sb.append("\nThe first ").append(common) + .append(" bytes are identical; the streams differ in length only, starting at offset ") + .append(common).append('.'); + } else { + sb.append("\nFirst difference at offset ").append(offset).append(" (0x").append(Integer.toHexString(offset)) + .append("): expected 0x").append(hexByte(expected[offset])).append(", got 0x") + .append(hexByte(actual[offset])).append('.'); + } + + int from = Math.max(0, offset - 16); + int to = offset + 16; + sb.append("\n expected[").append(from).append("..").append(Math.min(to, expected.length) - 1).append("]: ") + .append(hexDump(expected, from, to)); + sb.append("\n actual [").append(from).append("..").append(Math.min(to, actual.length) - 1).append("]: ") + .append(hexDump(actual, from, to)); + + Assert.fail(sb.toString()); + } + + private static String hexByte(byte b) { + return String.format("%02x", b); + } + + private static String hexDump(byte[] bytes, int from, int to) { + StringBuilder sb = new StringBuilder(); + for (int i = from; i < Math.min(to, bytes.length); i++) { + if (sb.length() > 0) { + sb.append(' '); + } + sb.append(hexByte(bytes[i])); + } + return sb.length() == 0 ? "" : sb.toString(); + } +} diff --git a/src/test/java/org/gephi/graph/impl/SerializationFixtureGenerator.java b/src/test/java/org/gephi/graph/impl/SerializationFixtureGenerator.java new file mode 100644 index 00000000..34a6c758 --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/SerializationFixtureGenerator.java @@ -0,0 +1,599 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import java.awt.Color; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Instant; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.gephi.graph.api.AttributeUtils; +import org.gephi.graph.api.Configuration; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Graph; +import org.gephi.graph.api.GraphModel; +import org.gephi.graph.api.GraphView; +import org.gephi.graph.api.Interval; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.Table; +import org.gephi.graph.api.TimeFormat; +import org.gephi.graph.api.TimeRepresentation; +import org.gephi.graph.api.types.IntervalBooleanMap; +import org.gephi.graph.api.types.IntervalByteMap; +import org.gephi.graph.api.types.IntervalCharMap; +import org.gephi.graph.api.types.IntervalDoubleMap; +import org.gephi.graph.api.types.IntervalFloatMap; +import org.gephi.graph.api.types.IntervalIntegerMap; +import org.gephi.graph.api.types.IntervalLongMap; +import org.gephi.graph.api.types.IntervalSet; +import org.gephi.graph.api.types.IntervalShortMap; +import org.gephi.graph.api.types.IntervalStringMap; +import org.gephi.graph.api.types.TimestampBooleanMap; +import org.gephi.graph.api.types.TimestampByteMap; +import org.gephi.graph.api.types.TimestampCharMap; +import org.gephi.graph.api.types.TimestampDoubleMap; +import org.gephi.graph.api.types.TimestampFloatMap; +import org.gephi.graph.api.types.TimestampIntegerMap; +import org.gephi.graph.api.types.TimestampLongMap; +import org.gephi.graph.api.types.TimestampSet; +import org.gephi.graph.api.types.TimestampShortMap; +import org.gephi.graph.api.types.TimestampStringMap; + +/** + * Builds and regenerates the golden serialization fixtures for the current minor version of graphstore. + *

+ * The fixtures live in src/test/resources/serialization/<MINOR>/ and are consumed by + * {@link SerializationCompatibilityTest}. See src/test/resources/serialization/README.md for the layout + * and the regeneration rules. + *

+ * The current minor's fixtures are byte-pinned, so every model built here must serialize to the same bytes on every run + * and every JVM. Run with: + * + *

+ * mvn -q test-compile
+ * mvn -q dependency:build-classpath -Dmdep.outputFile=target/test-cp.txt
+ * java -cp "target/classes:target/test-classes:$(cat target/test-cp.txt)" \
+ *     org.gephi.graph.impl.SerializationFixtureGenerator
+ * 
+ */ +public final class SerializationFixtureGenerator { + + /** + * The minor version the generated fixtures belong to. Must match the project version's MINOR. + */ + public static final String CURRENT_MINOR = "0.8"; + + /** + * Root of the fixture tree, relative to the project base directory. + */ + public static final String FIXTURE_ROOT = "src/test/resources/serialization"; + + public static final String FILE_EXTENSION = ".graphstore"; + + // Fixture names (file name without extension) + public static final String BASIC = "graph-basic"; + public static final String PARALLEL = "graph-parallel"; + public static final String TYPES_TIMESTAMP = "graph-types-timestamp"; + public static final String TYPES_INTERVAL = "graph-types-interval"; + public static final String VIEWS = "graph-views"; + + // Shared constants, also used by the assertions in SerializationCompatibilityTest + public static final String NODE_ID_1 = "node1"; + public static final String NODE_ID_2 = "node2"; + public static final String NODE_LABEL_1 = "Node 1"; + public static final String NODE_LABEL_2 = "Node 2"; + public static final String EDGE_ID_1 = "edge1"; + public static final String EDGE_ID_2 = "edge2"; + public static final String EDGE_TYPE_1 = "foo"; + public static final String EDGE_TYPE_2 = "bar"; + + private SerializationFixtureGenerator() { + // Utility + } + + /** + * Builds every fixture model for the current minor, keyed by fixture name. Iteration order is stable. + * + * @return ordered map of fixture name to freshly built graph model + */ + public static Map buildAll() { + Map models = new LinkedHashMap<>(); + models.put(BASIC, buildBasic()); + models.put(PARALLEL, buildParallel()); + models.put(TYPES_TIMESTAMP, buildTypeSurface(TimeRepresentation.TIMESTAMP)); + models.put(TYPES_INTERVAL, buildTypeSurface(TimeRepresentation.INTERVAL)); + models.put(VIEWS, buildViews()); + return models; + } + + /** + * Two nodes and one edge, mirroring the recipe used for the legacy 0.4 - 0.7 fixtures so the same content + * assertions apply across every minor. + * + * @return graph model + */ + public static GraphModel buildBasic() { + GraphModel gm = GraphModel.Factory.newInstance(); + Node node1 = gm.factory().newNode(NODE_ID_1); + Node node2 = gm.factory().newNode(NODE_ID_2); + + node1.setLabel(NODE_LABEL_1); + node1.setColor(Color.CYAN); + node1.setX(10.0f); + node1.setY(10.0f); + node1.setZ(1.0f); + node1.setSize(11.0f); + node1.setAlpha(0.5f); + + node2.setLabel(NODE_LABEL_2); + node2.setColor(Color.RED); + + gm.getGraph().addNode(node1); + gm.getGraph().addNode(node2); + + Edge edge = gm.factory().newEdge(EDGE_ID_1, node1, node2, 0, 1.0, true); + gm.getGraph().addEdge(edge); + + return gm; + } + + /** + * Two nodes and two parallel edges of distinct types, mirroring the legacy 0.4 - 0.7 recipe. + * + * @return graph model + */ + public static GraphModel buildParallel() { + GraphModel gm = GraphModel.Factory.newInstance(); + Node node1 = gm.factory().newNode(NODE_ID_1); + Node node2 = gm.factory().newNode(NODE_ID_2); + + node1.setLabel(NODE_LABEL_1); + node1.setColor(Color.CYAN); + node1.setX(10.0f); + node1.setY(10.0f); + node1.setZ(1.0f); + node1.setSize(11.0f); + node1.setAlpha(0.5f); + + node2.setLabel(NODE_LABEL_2); + node2.setColor(Color.RED); + + gm.getGraph().addNode(node1); + gm.getGraph().addNode(node2); + + int type1 = gm.addEdgeType(EDGE_TYPE_1); + int type2 = gm.addEdgeType(EDGE_TYPE_2); + + gm.getGraph().addEdge(gm.factory().newEdge(EDGE_ID_1, node1, node2, type1, 1.0, true)); + gm.getGraph().addEdge(gm.factory().newEdge(EDGE_ID_2, node1, node2, type2, 1.0, true)); + + return gm; + } + + /** + * The type-surface fixture: one node column per supported static type, one per dynamic type of the given time + * representation, dynamic edge weights, graph attributes, text properties and non-default element properties. + *

+ * The two time representations select different index-store implementations, hence one fixture each. + * + * @param timeRepresentation time representation to build for + * @return graph model + */ + public static GraphModel buildTypeSurface(TimeRepresentation timeRepresentation) { + boolean interval = timeRepresentation == TimeRepresentation.INTERVAL; + Configuration config = Configuration.builder().timeRepresentation(timeRepresentation) + .edgeWeightType(interval ? IntervalDoubleMap.class : TimestampDoubleMap.class).build(); + GraphModel gm = GraphModel.Factory.newInstance(config); + gm.setTimeFormat(interval ? TimeFormat.DATETIME : TimeFormat.DOUBLE); + gm.setTimeZone(ZoneId.of("Europe/Paris")); + + Table nodeTable = gm.getNodeTable(); + addStaticColumns(nodeTable); + addDynamicColumns(nodeTable, timeRepresentation); + addCollectionColumns(nodeTable); + + Table edgeTable = gm.getEdgeTable(); + addStaticColumns(edgeTable); + addDynamicColumns(edgeTable, timeRepresentation); + + Graph graph = gm.getGraph(); + Node node1 = gm.factory().newNode(NODE_ID_1); + Node node2 = gm.factory().newNode(NODE_ID_2); + node1.setLabel(NODE_LABEL_1); + node2.setLabel(NODE_LABEL_2); + graph.addNode(node1); + graph.addNode(node2); + + // Non-default element and text properties + node1.setColor(new Color(12, 34, 56)); + node1.setAlpha(0.75f); + node1.setPosition(1.5f, -2.5f, 3.5f); + node1.setSize(7.25f); + node1.setFixed(true); + node1.getTextProperties().setColor(new Color(200, 100, 50)); + node1.getTextProperties().setSize(13.5f); + node1.getTextProperties().setVisible(false); + node1.getTextProperties().setText("node one text"); + node1.getTextProperties().setDimensions(42.0f, 24.0f); + + // Fill every static and dynamic column on node1, leave node2 on defaults + setStaticValues(node1); + setDynamicValues(node1, timeRepresentation); + setCollectionValues(node1); + if (interval) { + node1.addInterval(new Interval(1.0, 5.0)); + node2.addInterval(new Interval(2.0, 3.0)); + } else { + node1.addTimestamp(1.0); + node1.addTimestamp(4.0); + node2.addTimestamp(2.0); + } + + int typeFoo = gm.addEdgeType(EDGE_TYPE_1); + Edge edge1 = gm.factory().newEdge(EDGE_ID_1, node1, node2, typeFoo, 1.0, true); + graph.addEdge(edge1); + edge1.setLabel("Edge 1"); + edge1.setColor(new Color(9, 8, 7)); + edge1.getTextProperties().setText("edge one text"); + edge1.getTextProperties().setSize(3.5f); + setStaticValues(edge1); + setDynamicValues(edge1, timeRepresentation); + + // Dynamic edge weight + if (interval) { + edge1.setWeight(2.5, new Interval(1.0, 2.0)); + edge1.setWeight(3.5, new Interval(3.0, 4.0)); + } else { + edge1.setWeight(2.5, 1.0); + edge1.setWeight(3.5, 3.0); + } + + // Graph attributes. Safe to byte-pin: GraphAttributesImpl keeps a canonical (sorted) order. + graph.setAttribute("attr-string", "graph level"); + graph.setAttribute("attr-int", 42); + graph.setAttribute("attr-double", 3.5); + graph.setAttribute("attr-char", 'g'); + graph.setAttribute("attr-char-array", new char[] { 'a', 'b' }); + graph.setAttribute("attr-instant", Instant.ofEpochSecond(1_600_000_000L, 123)); + if (interval) { + graph.setAttribute("attr-dynamic", "first", new Interval(1.0, 2.0)); + graph.setAttribute("attr-dynamic", "second", new Interval(3.0, 4.0)); + } else { + graph.setAttribute("attr-dynamic", "first", 1.0); + graph.setAttribute("attr-dynamic", "second", 3.0); + } + + return gm; + } + + /** + * Views, edge types, mixed directedness and self-loops. + * + * @return graph model + */ + public static GraphModel buildViews() { + GraphModel gm = GraphModel.Factory.newInstance(); + + Table nodeTable = gm.getNodeTable(); + nodeTable.addColumn("weight", Double.class); + nodeTable.addColumn("tag", String.class); + + Graph graph = gm.getGraph(); + Node[] nodes = new Node[5]; + for (int i = 0; i < nodes.length; i++) { + nodes[i] = gm.factory().newNode("n" + i); + nodes[i].setLabel("Node " + i); + nodes[i].setAttribute("weight", (double) i); + nodes[i].setAttribute("tag", "tag" + i); + graph.addNode(nodes[i]); + } + + int typeFoo = gm.addEdgeType(EDGE_TYPE_1); + int typeBar = gm.addEdgeType(EDGE_TYPE_2); + + // Directed, undirected and self-loop edges over multiple types + graph.addEdge(gm.factory().newEdge("e0", nodes[0], nodes[1], 0, 1.0, true)); + graph.addEdge(gm.factory().newEdge("e1", nodes[1], nodes[2], typeFoo, 2.0, true)); + graph.addEdge(gm.factory().newEdge("e2", nodes[2], nodes[3], typeBar, 3.0, false)); + graph.addEdge(gm.factory().newEdge("e3", nodes[3], nodes[4], typeFoo, 4.0, false)); + graph.addEdge(gm.factory().newEdge("e4", nodes[4], nodes[4], typeBar, 5.0, true)); + + // A node+edge view holding a subset + GraphView nodeAndEdgeView = gm.createView(); + Graph subGraph = gm.getGraph(nodeAndEdgeView); + subGraph.addNode(nodes[0]); + subGraph.addNode(nodes[1]); + subGraph.addNode(nodes[2]); + subGraph.addEdge(graph.getEdge("e0")); + subGraph.setAttribute("view-attr", "on the view"); + subGraph.setAttribute("view-count", 3); + + // A node-only view + GraphView nodeOnlyView = gm.createView(true, false); + Graph nodeOnlyGraph = gm.getGraph(nodeOnlyView); + nodeOnlyGraph.addNode(nodes[3]); + nodeOnlyGraph.addNode(nodes[4]); + + // The visible view stays on the main view. GraphViewStore.visibleView is not part of the serialized format, + // see Serialization.serializeViewStore. + + graph.setAttribute("graph-attr", "main graph"); + + return gm; + } + + // Column and value helpers + + /** + * Every supported static type, in a fixed order. Boxed array types are intentionally included: they standardize to + * their primitive counterparts, which is part of the surface worth pinning. + */ + private static void addStaticColumns(Table table) { + table.addColumn("t_boolean", Boolean.class); + table.addColumn("t_integer", Integer.class); + table.addColumn("t_short", Short.class); + table.addColumn("t_long", Long.class); + table.addColumn("t_biginteger", BigInteger.class); + table.addColumn("t_byte", Byte.class); + table.addColumn("t_float", Float.class); + table.addColumn("t_double", Double.class); + table.addColumn("t_bigdecimal", BigDecimal.class); + table.addColumn("t_character", Character.class); + table.addColumn("t_string", String.class); + table.addColumn("t_instant", Instant.class); + + table.addColumn("t_boolean_array", boolean[].class); + table.addColumn("t_int_array", int[].class); + table.addColumn("t_short_array", short[].class); + table.addColumn("t_long_array", long[].class); + table.addColumn("t_biginteger_array", BigInteger[].class); + table.addColumn("t_byte_array", byte[].class); + table.addColumn("t_float_array", float[].class); + table.addColumn("t_double_array", double[].class); + table.addColumn("t_bigdecimal_array", BigDecimal[].class); + table.addColumn("t_char_array", char[].class); + table.addColumn("t_string_array", String[].class); + + // Boxed arrays, standardized to primitive arrays by the table + table.addColumn("t_boxed_boolean_array", Boolean[].class); + table.addColumn("t_boxed_character_array", Character[].class); + } + + private static void addDynamicColumns(Table table, TimeRepresentation timeRepresentation) { + if (timeRepresentation == TimeRepresentation.INTERVAL) { + table.addColumn("t_interval_set", IntervalSet.class); + table.addColumn("t_interval_boolean", IntervalBooleanMap.class); + table.addColumn("t_interval_integer", IntervalIntegerMap.class); + table.addColumn("t_interval_short", IntervalShortMap.class); + table.addColumn("t_interval_long", IntervalLongMap.class); + table.addColumn("t_interval_byte", IntervalByteMap.class); + table.addColumn("t_interval_float", IntervalFloatMap.class); + table.addColumn("t_interval_double", IntervalDoubleMap.class); + table.addColumn("t_interval_char", IntervalCharMap.class); + table.addColumn("t_interval_string", IntervalStringMap.class); + } else { + table.addColumn("t_timestamp_set", TimestampSet.class); + table.addColumn("t_timestamp_boolean", TimestampBooleanMap.class); + table.addColumn("t_timestamp_integer", TimestampIntegerMap.class); + table.addColumn("t_timestamp_short", TimestampShortMap.class); + table.addColumn("t_timestamp_long", TimestampLongMap.class); + table.addColumn("t_timestamp_byte", TimestampByteMap.class); + table.addColumn("t_timestamp_float", TimestampFloatMap.class); + table.addColumn("t_timestamp_double", TimestampDoubleMap.class); + table.addColumn("t_timestamp_char", TimestampCharMap.class); + table.addColumn("t_timestamp_string", TimestampStringMap.class); + } + } + + /** + * List, Set and Map columns. Only the List gets a value: lists round-trip order-preserving, whereas generic sets + * and maps are serialized in hash order and come back as fastutil implementations, so they cannot be byte-pinned. + */ + private static void addCollectionColumns(Table table) { + table.addColumn("t_list", List.class); + table.addColumn("t_set", java.util.Set.class); + table.addColumn("t_map", Map.class); + } + + private static void setStaticValues(org.gephi.graph.api.Element element) { + element.setAttribute("t_boolean", Boolean.TRUE); + element.setAttribute("t_integer", 123456); + element.setAttribute("t_short", (short) -12); + element.setAttribute("t_long", 9876543210L); + element.setAttribute("t_biginteger", new BigInteger("123456789012345678901234567890")); + element.setAttribute("t_byte", (byte) 7); + element.setAttribute("t_float", 1.25f); + element.setAttribute("t_double", -2.5); + element.setAttribute("t_bigdecimal", new BigDecimal("1234567890.0987654321")); + element.setAttribute("t_character", 'Z'); + element.setAttribute("t_string", "hello é中文"); + element.setAttribute("t_instant", Instant.ofEpochSecond(1_500_000_000L, 42)); + + element.setAttribute("t_boolean_array", new boolean[] { true, false, true }); + element.setAttribute("t_int_array", new int[] { 1, -2, 3 }); + element.setAttribute("t_short_array", new short[] { 4, -5 }); + element.setAttribute("t_long_array", new long[] { 6L, -7L }); + element.setAttribute("t_biginteger_array", new BigInteger[] { BigInteger.ONE, new BigInteger("-99") }); + element.setAttribute("t_byte_array", new byte[] { 8, -9 }); + element.setAttribute("t_float_array", new float[] { 1.5f, -2.5f }); + element.setAttribute("t_double_array", new double[] { 3.5, -4.5 }); + element.setAttribute("t_bigdecimal_array", new BigDecimal[] { BigDecimal.ONE, new BigDecimal("-0.5") }); + element.setAttribute("t_char_array", new char[] { 'a', 'é', '中' }); + // No null elements: Serialization.serializeStringArray does not support them + element.setAttribute("t_string_array", new String[] { "one", "two", "three" }); + + element.setAttribute("t_boxed_boolean_array", new Boolean[] { Boolean.FALSE, Boolean.TRUE }); + element.setAttribute("t_boxed_character_array", new Character[] { 'x', 'y' }); + } + + private static void setDynamicValues(org.gephi.graph.api.Element element, TimeRepresentation timeRepresentation) { + if (timeRepresentation == TimeRepresentation.INTERVAL) { + IntervalSet set = new IntervalSet(); + set.add(new Interval(1.0, 2.0)); + set.add(new Interval(3.0, 4.0)); + element.setAttribute("t_interval_set", set); + + IntervalBooleanMap booleanMap = new IntervalBooleanMap(); + booleanMap.put(new Interval(1.0, 2.0), true); + booleanMap.put(new Interval(3.0, 4.0), false); + element.setAttribute("t_interval_boolean", booleanMap); + + IntervalIntegerMap integerMap = new IntervalIntegerMap(); + integerMap.put(new Interval(1.0, 2.0), 10); + integerMap.put(new Interval(3.0, 4.0), -20); + element.setAttribute("t_interval_integer", integerMap); + + IntervalShortMap shortMap = new IntervalShortMap(); + shortMap.put(new Interval(1.0, 2.0), (short) 30); + element.setAttribute("t_interval_short", shortMap); + + IntervalLongMap longMap = new IntervalLongMap(); + longMap.put(new Interval(1.0, 2.0), 40L); + element.setAttribute("t_interval_long", longMap); + + IntervalByteMap byteMap = new IntervalByteMap(); + byteMap.put(new Interval(1.0, 2.0), (byte) 50); + element.setAttribute("t_interval_byte", byteMap); + + IntervalFloatMap floatMap = new IntervalFloatMap(); + floatMap.put(new Interval(1.0, 2.0), 6.5f); + element.setAttribute("t_interval_float", floatMap); + + IntervalDoubleMap doubleMap = new IntervalDoubleMap(); + doubleMap.put(new Interval(1.0, 2.0), 7.5); + element.setAttribute("t_interval_double", doubleMap); + + IntervalCharMap charMap = new IntervalCharMap(); + charMap.put(new Interval(1.0, 2.0), 'c'); + charMap.put(new Interval(3.0, 4.0), '中'); + element.setAttribute("t_interval_char", charMap); + + IntervalStringMap stringMap = new IntervalStringMap(); + stringMap.put(new Interval(1.0, 2.0), "alpha"); + stringMap.put(new Interval(3.0, 4.0), "beta"); + element.setAttribute("t_interval_string", stringMap); + } else { + TimestampSet set = new TimestampSet(); + set.add(1.0); + set.add(3.0); + element.setAttribute("t_timestamp_set", set); + + TimestampBooleanMap booleanMap = new TimestampBooleanMap(); + booleanMap.put(1.0, true); + booleanMap.put(3.0, false); + element.setAttribute("t_timestamp_boolean", booleanMap); + + TimestampIntegerMap integerMap = new TimestampIntegerMap(); + integerMap.put(1.0, 10); + integerMap.put(3.0, -20); + element.setAttribute("t_timestamp_integer", integerMap); + + TimestampShortMap shortMap = new TimestampShortMap(); + shortMap.put(1.0, (short) 30); + element.setAttribute("t_timestamp_short", shortMap); + + TimestampLongMap longMap = new TimestampLongMap(); + longMap.put(1.0, 40L); + element.setAttribute("t_timestamp_long", longMap); + + TimestampByteMap byteMap = new TimestampByteMap(); + byteMap.put(1.0, (byte) 50); + element.setAttribute("t_timestamp_byte", byteMap); + + TimestampFloatMap floatMap = new TimestampFloatMap(); + floatMap.put(1.0, 6.5f); + element.setAttribute("t_timestamp_float", floatMap); + + TimestampDoubleMap doubleMap = new TimestampDoubleMap(); + doubleMap.put(1.0, 7.5); + element.setAttribute("t_timestamp_double", doubleMap); + + TimestampCharMap charMap = new TimestampCharMap(); + charMap.put(1.0, 'c'); + charMap.put(3.0, '中'); + element.setAttribute("t_timestamp_char", charMap); + + TimestampStringMap stringMap = new TimestampStringMap(); + stringMap.put(1.0, "alpha"); + stringMap.put(3.0, "beta"); + element.setAttribute("t_timestamp_string", stringMap); + } + } + + private static void setCollectionValues(org.gephi.graph.api.Element element) { + List list = new ArrayList<>(); + list.add("first"); + list.add("second"); + element.setAttribute("t_list", list); + // t_set and t_map stay null, see addCollectionColumns + } + + // Serialization helpers + + /** + * Serializes the model through the production path, exactly as the fixture files on disk were written. + * + * @param graphModel model to serialize + * @return serialized bytes + * @throws IOException if an io error occurs + */ + public static byte[] serialize(GraphModel graphModel) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos)) { + GraphModel.Serialization.write(dos, graphModel); + } + return baos.toByteArray(); + } + + /** + * Regenerates every fixture of the current minor. + * + * @param args optional single argument, the fixture root directory (defaults to {@link #FIXTURE_ROOT}) + * @throws IOException if an io error occurs + */ + public static void main(String[] args) throws IOException { + File root = new File(args.length > 0 ? args[0] : FIXTURE_ROOT, CURRENT_MINOR); + if (!root.exists() && !root.mkdirs()) { + throw new IOException("Can't create the fixture folder " + root.getAbsolutePath()); + } + System.out.println("Writing " + CURRENT_MINOR + " fixtures to " + root.getAbsolutePath()); + for (Map.Entry entry : buildAll().entrySet()) { + File file = new File(root, entry.getKey() + FILE_EXTENSION); + byte[] bytes = serialize(entry.getValue()); + try (FileOutputStream fos = new FileOutputStream(file)) { + fos.write(bytes); + } + System.out.println(" " + file.getName() + " (" + bytes.length + " bytes)"); + } + + // Cheap self-check: every declared type must actually be supported + for (Class type : new Class[] { Boolean.class, Integer.class, Short.class, Long.class, BigInteger.class, Byte.class, Float.class, Double.class, BigDecimal.class, Character.class, String.class, Instant.class, boolean[].class, int[].class, short[].class, long[].class, BigInteger[].class, byte[].class, float[].class, double[].class, BigDecimal[].class, char[].class, String[].class, List.class, java.util.Set.class, Map.class }) { + if (!AttributeUtils.isSupported(type)) { + throw new IllegalStateException("Type no longer supported: " + type); + } + } + System.out.println("Done."); + } +} diff --git a/store/src/test/java/org/gephi/graph/impl/SerializationTest.java b/src/test/java/org/gephi/graph/impl/SerializationTest.java similarity index 52% rename from store/src/test/java/org/gephi/graph/impl/SerializationTest.java rename to src/test/java/org/gephi/graph/impl/SerializationTest.java index 5d6bc39f..6697ed39 100644 --- a/store/src/test/java/org/gephi/graph/impl/SerializationTest.java +++ b/src/test/java/org/gephi/graph/impl/SerializationTest.java @@ -15,7 +15,6 @@ */ package org.gephi.graph.impl; -import cern.colt.bitvector.BitVector; import it.unimi.dsi.fastutil.booleans.BooleanArrayList; import it.unimi.dsi.fastutil.booleans.BooleanOpenHashSet; import it.unimi.dsi.fastutil.bytes.Byte2ObjectOpenHashMap; @@ -41,20 +40,39 @@ import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; import it.unimi.dsi.fastutil.shorts.ShortArrayList; import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; import java.io.DataOutput; +import java.io.DataOutputStream; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.Instant; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; +import java.util.Random; import java.util.Set; +import java.util.Spliterator; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import org.gephi.graph.api.Column; import org.gephi.graph.api.Configuration; +import org.gephi.graph.api.GraphModel; +import org.gephi.graph.api.Node; import org.gephi.graph.api.Origin; import org.gephi.graph.api.Estimator; import org.gephi.graph.api.TimeFormat; @@ -71,6 +89,7 @@ import org.gephi.graph.api.Edge; import org.gephi.graph.api.Interval; import org.gephi.graph.api.TimeRepresentation; +import org.gephi.graph.api.UnsupportedFormatVersionException; import org.gephi.graph.api.types.IntervalBooleanMap; import org.gephi.graph.api.types.IntervalByteMap; import org.gephi.graph.api.types.IntervalCharMap; @@ -82,7 +101,6 @@ import org.gephi.graph.api.types.IntervalShortMap; import org.gephi.graph.api.types.IntervalStringMap; import org.gephi.graph.impl.utils.DataInputOutput; -import org.joda.time.DateTimeZone; import org.testng.Assert; import org.testng.annotations.Test; @@ -106,11 +124,12 @@ public void testEdgeStoreMixed() throws IOException, ClassNotFoundException { Serialization ser = new Serialization(graphModel); byte[] buf = ser.serialize(graphStore); - graphStore.clear(); + GraphModelImpl graphModel2 = new GraphModelImpl(); + ser = new Serialization(graphModel2); GraphStore l = (GraphStore) ser.deserialize(buf); - Assert.assertTrue(nodeStore.equals(l.nodeStore)); - Assert.assertTrue(edgeStore.equals(l.edgeStore)); + Assert.assertTrue(nodeStore.deepEquals(l.nodeStore)); + Assert.assertTrue(edgeStore.deepEquals(l.edgeStore)); } @Test @@ -127,11 +146,12 @@ public void testEdgeStoreMultipleTypes() throws IOException, ClassNotFoundExcept Serialization ser = new Serialization(graphModel); byte[] buf = ser.serialize(graphStore); - graphStore.clear(); + GraphModelImpl graphModel2 = new GraphModelImpl(); + ser = new Serialization(graphModel2); GraphStore l = (GraphStore) ser.deserialize(buf); - Assert.assertTrue(nodeStore.equals(l.nodeStore)); - Assert.assertTrue(edgeStore.equals(l.edgeStore)); + Assert.assertTrue(nodeStore.deepEquals(l.nodeStore)); + Assert.assertTrue(edgeStore.deepEquals(l.edgeStore)); } @Test @@ -143,16 +163,17 @@ public void testEdgeStore() throws IOException, ClassNotFoundException { EdgeStore edgeStore = graphStore.edgeStore; NodeImpl[] nodes = GraphGenerator.generateSmallNodeList(); nodeStore.addAll(Arrays.asList(nodes)); - EdgeImpl[] edges = GraphGenerator.generateSmallEdgeList(); + EdgeImpl[] edges = GraphGenerator.generateEdgeList(nodeStore, 100, 0, true, true, false); edgeStore.addAll(Arrays.asList(edges)); Serialization ser = new Serialization(graphModel); byte[] buf = ser.serialize(graphStore); - graphStore.clear(); + GraphModelImpl graphModel2 = new GraphModelImpl(); + ser = new Serialization(graphModel2); GraphStore l = (GraphStore) ser.deserialize(buf); - Assert.assertTrue(nodeStore.equals(l.nodeStore)); - Assert.assertTrue(edgeStore.equals(l.edgeStore)); + Assert.assertTrue(nodeStore.deepEquals(l.nodeStore)); + Assert.assertTrue(edgeStore.deepEquals(l.edgeStore)); } @Test @@ -198,7 +219,7 @@ public void testNode() throws IOException, ClassNotFoundException { ser = new Serialization(graphModel); NodeImpl l = (NodeImpl) ser.deserialize(buf); Assert.assertTrue(node.equals(l)); - Assert.assertTrue(Arrays.deepEquals(l.attributes, node.attributes)); + Assert.assertTrue(Arrays.deepEquals(l.getAttributes(), node.getAttributes())); } @Test @@ -313,14 +334,14 @@ public void testGraphView() throws IOException, ClassNotFoundException { } @Test - public void testBitVector() throws IOException, ClassNotFoundException { - BitVector bitVector = new BitVector(10); + public void testBitSet() throws IOException, ClassNotFoundException { + BitSet bitVector = new BitSet(10); bitVector.set(1); bitVector.set(4); Serialization ser = new Serialization(null); byte[] buf = ser.serialize(bitVector); - BitVector l = (BitVector) ser.deserialize(buf); + BitSet l = (BitSet) ser.deserialize(buf); Assert.assertEquals(bitVector, l); } @@ -341,7 +362,7 @@ public void testGraphVersion() throws IOException, ClassNotFoundException { @Test public void testTextProperties() throws IOException, ClassNotFoundException { TextPropertiesImpl textProperties = new TextPropertiesImpl(); - textProperties.rgba = 100; + textProperties.rgba = 0x01FF0000; textProperties.size = 3f; textProperties.text = "foo"; textProperties.visible = true; @@ -363,7 +384,7 @@ public void testNodeProperties() throws IOException, ClassNotFoundException { nodeProperties.rgba = 100; nodeProperties.size = 4f; nodeProperties.fixed = true; - nodeProperties.textProperties.rgba = 200; + nodeProperties.textProperties.rgba = 0x01FF0000; nodeProperties.textProperties.size = 5f; nodeProperties.textProperties.text = "foo"; nodeProperties.textProperties.visible = true; @@ -377,8 +398,8 @@ public void testNodeProperties() throws IOException, ClassNotFoundException { @Test public void testEdgeProperties() throws IOException, ClassNotFoundException { EdgeImpl.EdgePropertiesImpl edgeProperties = new EdgeImpl.EdgePropertiesImpl(); - edgeProperties.rgba = 100; - edgeProperties.textProperties.rgba = 200; + edgeProperties.rgba = 0x01FF0000; + edgeProperties.textProperties.rgba = 0x01FF0000; edgeProperties.textProperties.size = 5f; edgeProperties.textProperties.text = "foo"; edgeProperties.textProperties.visible = true; @@ -651,6 +672,15 @@ public void testIntervalStringMap() throws IOException, ClassNotFoundException { Assert.assertEquals(timestampMap, l); } + @Test + public void testInstant() throws IOException, ClassNotFoundException { + Instant instant = Instant.ofEpochSecond(1234567890, 44553); + Serialization ser = new Serialization(null); + byte[] buf = ser.serialize(instant); + Instant l = (Instant) ser.deserialize(buf); + Assert.assertEquals(instant, l); + } + @Test public void testGraphAttributes() throws IOException, ClassNotFoundException { GraphAttributesImpl graphAttributes = new GraphAttributesImpl(); @@ -680,14 +710,19 @@ public void testTimeFormat() throws IOException, ClassNotFoundException { public void testTimeZone() throws IOException, ClassNotFoundException { GraphModelImpl graphModel = new GraphModelImpl(); GraphStore store = graphModel.store; - store.timeZone = DateTimeZone.forID("+01:30"); + store.timeZone = ZoneId.of("+01:30"); Serialization ser = new Serialization(graphModel); byte[] buf = ser.serialize(store.timeZone); - DateTimeZone l = (DateTimeZone) ser.deserialize(buf); - Assert.assertEquals(DateTimeZone.forID("+01:30"), l); + ZoneId l = (ZoneId) ser.deserialize(buf); + Assert.assertEquals(ZoneId.of("+01:30"), l); } + /** + * The time store block is derived state, rebuilt from the elements as they are inserted. Its content is not + * written, read standalone it carries nothing, and it must consume exactly its own bytes: + * Serialization.deserialize(byte[]) throws if any are left. + */ @Test public void testTimestampStore() throws IOException, ClassNotFoundException { GraphModelImpl graphModel = new GraphModelImpl(); @@ -701,19 +736,21 @@ public void testTimestampStore() throws IOException, ClassNotFoundException { timestampStore.edgeIndexStore.add(3.0); timestampStore.edgeIndexStore.add(4.0); - Serialization ser = new Serialization(graphModel); - byte[] buf = ser.serialize(timestampStore); + Assert.assertFalse(timestampStore.isEmpty()); + byte[] buf = new Serialization(graphModel).serialize(timestampStore); - graphModel = new GraphModelImpl(); - ser = new Serialization(graphModel); - TimeStore l = (TimeStore) ser.deserialize(buf); - Assert.assertTrue(timestampStore.deepEquals(l)); + // The index is derived state: its content leaves no trace in the bytes and reading yields an empty store + GraphModelImpl empty = new GraphModelImpl(); + Assert.assertEquals(buf, new Serialization(empty).serialize(empty.store.timeStore)); + + GraphModelImpl readModel = new GraphModelImpl(); + TimeStore l = (TimeStore) new Serialization(readModel).deserialize(buf); + Assert.assertTrue(l.isEmpty()); } @Test public void testIntervalStore() throws IOException, ClassNotFoundException { - Configuration config = new Configuration(); - config.setTimeRepresentation(TimeRepresentation.INTERVAL); + Configuration config = Configuration.builder().timeRepresentation(TimeRepresentation.INTERVAL).build(); GraphModelImpl graphModel = new GraphModelImpl(config); GraphStore store = graphModel.store; TimeStore timestampStore = store.timeStore; @@ -725,31 +762,252 @@ public void testIntervalStore() throws IOException, ClassNotFoundException { timestampStore.edgeIndexStore.add(new Interval(2.0, 3.0)); timestampStore.edgeIndexStore.add(new Interval(2.0, 3.0)); - Serialization ser = new Serialization(graphModel); - byte[] buf = ser.serialize(timestampStore); + Assert.assertFalse(timestampStore.isEmpty()); + byte[] buf = new Serialization(graphModel).serialize(timestampStore); - graphModel = new GraphModelImpl(config); - ser = new Serialization(graphModel); - TimeStore l = (TimeStore) ser.deserialize(buf); - Assert.assertTrue(timestampStore.deepEquals(l)); + // The index is derived state: its content leaves no trace in the bytes and reading yields an empty store + GraphModelImpl empty = new GraphModelImpl(config); + Assert.assertEquals(buf, new Serialization(empty).serialize(empty.store.timeStore)); + + GraphModelImpl readModel = new GraphModelImpl(config); + TimeStore l = (TimeStore) new Serialization(readModel).deserialize(buf); + Assert.assertTrue(l.isEmpty()); } @Test - public void testConfiguration() throws IOException, ClassNotFoundException { - GraphModelImpl graphModel = new GraphModelImpl(); - Configuration configuration = graphModel.configuration; + public void testGraphStoreTimestampIndexCounts() throws IOException, ClassNotFoundException { + assertTimeIndexCountsSurviveRoundTrip(TimeRepresentation.TIMESTAMP); + } + + @Test + public void testGraphStoreIntervalIndexCounts() throws IOException, ClassNotFoundException { + assertTimeIndexCountsSurviveRoundTrip(TimeRepresentation.INTERVAL); + } + + @Test + public void testGraphStoreTimestampIndexRemoveAfterRoundTrip() throws IOException, ClassNotFoundException { + assertTimeIsRemovedFromIndexAfterRoundTrip(TimeRepresentation.TIMESTAMP); + } + + @Test + public void testGraphStoreIntervalIndexRemoveAfterRoundTrip() throws IOException, ClassNotFoundException { + assertTimeIsRemovedFromIndexAfterRoundTrip(TimeRepresentation.INTERVAL); + } - configuration.setNodeIdType(Float.class); - configuration.setEdgeIdType(Long.class); - configuration.setTimeRepresentation(TimeRepresentation.INTERVAL); + @Test + public void testGraphStoreTimestampIndexIsRebuiltOnRead() throws IOException, ClassNotFoundException { + assertTimeIndexIsRebuiltOnRead(TimeRepresentation.TIMESTAMP); + } + + @Test + public void testGraphStoreIntervalIndexIsRebuiltOnRead() throws IOException, ClassNotFoundException { + assertTimeIndexIsRebuiltOnRead(TimeRepresentation.INTERVAL); + } + + /** + * Files written before #288 carry reference counts above the number of references, and time values nothing points + * at. Reading rebuilds the index from the elements, which drops both. The index is inflated directly here, since + * the write path no longer produces that state. + */ + private void assertTimeIndexIsRebuiltOnRead(TimeRepresentation timeRepresentation) throws IOException, ClassNotFoundException { + GraphModelImpl graphModel = new GraphModelImpl( + Configuration.builder().timeRepresentation(timeRepresentation).build()); + GraphStore graphStore = graphModel.store; + boolean interval = timeRepresentation.equals(TimeRepresentation.INTERVAL); + + Column column = graphStore.nodeTable + .addColumn("dynamic", interval ? IntervalIntegerMap.class : TimestampIntegerMap.class); + NodeImpl node1 = (NodeImpl) graphStore.factory.newNode("1"); + NodeImpl node2 = (NodeImpl) graphStore.factory.newNode("2"); + graphStore.addNode(node1); + graphStore.addNode(node2); + + setTimeAttribute(node1, column, timeRepresentation, 1.0, 10); + setTimeAttribute(node1, column, timeRepresentation, 2.0, 20); + setTimeAttribute(node1, column, timeRepresentation, 3.0, 30); + addTime(node2, timeRepresentation, 2.0); + + Object first = timeKey(timeRepresentation, 1.0); + Object shared = timeKey(timeRepresentation, 2.0); + Object last = timeKey(timeRepresentation, 3.0); + Object stranded = timeKey(timeRepresentation, 7.0); + + // Emulate a store written before #288: a count above the number of references, and a time no element holds + TimeIndexStore nodeIndexStore = graphStore.timeStore.nodeIndexStore; + nodeIndexStore.add(first); + nodeIndexStore.add(stranded); + Assert.assertEquals(nodeIndexStore.countMap[(Integer) nodeIndexStore.timeSortedMap.get(first)], 2); + Assert.assertTrue(nodeIndexStore.contains(stranded)); + + GraphStore read = roundTrip(graphStore, timeRepresentation); + TimeIndexStore readIndexStore = read.timeStore.nodeIndexStore; + + // Rebuilt from the elements: node 1 holds 1.0, 2.0 and 3.0, node 2 holds 2.0 + Assert.assertFalse(readIndexStore.contains(stranded)); + Assert.assertEquals(readIndexStore.size(), 3); + Assert.assertEquals(readIndexStore.countMap[(Integer) readIndexStore.timeSortedMap.get(first)], 1); + Assert.assertEquals(readIndexStore.countMap[(Integer) readIndexStore.timeSortedMap.get(shared)], 2); + Assert.assertEquals(readIndexStore.countMap[(Integer) readIndexStore.timeSortedMap.get(last)], 1); + + // The only reference to 3.0 is node 1's map, so dropping it frees the time + removeTimeAttribute(read.getNode("1"), read.nodeTable.getColumn("dynamic"), timeRepresentation, 3.0); + Assert.assertFalse(readIndexStore.contains(last)); + } + + private void assertTimeIndexCountsSurviveRoundTrip(TimeRepresentation timeRepresentation) throws IOException, ClassNotFoundException { + GraphStore graphStore = newTimeIndexGraphStore(timeRepresentation); + GraphStore read = roundTrip(graphStore, timeRepresentation); + + assertTimeIndexCountsEqual(graphStore.timeStore.nodeIndexStore, read.timeStore.nodeIndexStore); + assertTimeIndexCountsEqual(graphStore.timeStore.edgeIndexStore, read.timeStore.edgeIndexStore); + } + + private void assertTimeIsRemovedFromIndexAfterRoundTrip(TimeRepresentation timeRepresentation) throws IOException, ClassNotFoundException { + GraphStore read = roundTrip(newTimeIndexGraphStore(timeRepresentation), timeRepresentation); + TimeIndexStore nodeIndexStore = read.timeStore.nodeIndexStore; + + // Time 2.0 is referenced by two nodes and nothing else + Object timeKey = timeKey(timeRepresentation, 2.0); + Assert.assertTrue(nodeIndexStore.contains(timeKey)); + int timeIndex = (Integer) nodeIndexStore.timeSortedMap.get(timeKey); + + removeTime(read.getNode("1"), timeRepresentation, 2.0); + Assert.assertTrue(nodeIndexStore.contains(timeKey)); + removeTime(read.getNode("2"), timeRepresentation, 2.0); + + Assert.assertFalse(nodeIndexStore.contains(timeKey)); + Assert.assertTrue(nodeIndexStore.garbageQueue.contains(timeIndex)); + } + + /** + * Compares the time values and their reference counts. Slot numbering and the garbage queue are allocation state, + * not part of the contract: the index is rebuilt from the elements, so slots may be numbered differently. + */ + private static void assertTimeIndexCountsEqual(TimeIndexStore expected, TimeIndexStore actual) { + // With equal sizes, resolving every expected time value in actual makes this a full set comparison + Assert.assertEquals(actual.size(), expected.size()); + for (Object o : expected.timeSortedMap.entrySet()) { + Map.Entry entry = (Map.Entry) o; + Object timeKey = entry.getKey(); + Object actualTimeIndex = actual.timeSortedMap.get(timeKey); + Assert.assertNotNull(actualTimeIndex, "Missing time index for " + timeKey); + Assert.assertEquals(actual.countMap[(Integer) actualTimeIndex], expected.countMap[(Integer) entry + .getValue()], "Reference count for " + timeKey); + } + } + + /** + * Builds a store with element time sets, a dynamic column, times shared by several elements, a parallel edge, a + * freed time index and a dynamic time set twice. + */ + private static GraphStore newTimeIndexGraphStore(TimeRepresentation timeRepresentation) { + GraphModelImpl graphModel = new GraphModelImpl( + Configuration.builder().timeRepresentation(timeRepresentation).build()); + GraphStore graphStore = graphModel.store; + boolean interval = timeRepresentation.equals(TimeRepresentation.INTERVAL); + + Column column = graphStore.nodeTable + .addColumn("dynamic", interval ? IntervalIntegerMap.class : TimestampIntegerMap.class); + + NodeImpl node1 = (NodeImpl) graphStore.factory.newNode("1"); + NodeImpl node2 = (NodeImpl) graphStore.factory.newNode("2"); + NodeImpl node3 = (NodeImpl) graphStore.factory.newNode("3"); + graphStore.addNode(node1); + graphStore.addNode(node2); + graphStore.addNode(node3); + + EdgeImpl edge1 = (EdgeImpl) graphStore.factory.newEdge("1", node1, node2, 0, 1.0, true); + EdgeImpl edge2 = (EdgeImpl) graphStore.factory + .newEdge("2", node1, node2, graphStore.edgeTypeStore.addType("type"), 1.0, true); + graphStore.addEdge(edge1); + graphStore.addEdge(edge2); + + addTime(node1, timeRepresentation, 1.0); + addTime(node1, timeRepresentation, 2.0); + addTime(node2, timeRepresentation, 2.0); + addTime(edge1, timeRepresentation, 1.0); + addTime(edge2, timeRepresentation, 1.0); + addTime(edge2, timeRepresentation, 3.0); + + // Frees a time index, leaving the garbage queue non-empty + addTime(node3, timeRepresentation, 4.0); + removeTime(node3, timeRepresentation, 4.0); + + if (interval) { + IntervalIntegerMap map = new IntervalIntegerMap(); + map.put(new Interval(5.0, 5.0), 10); + map.put(new Interval(6.0, 6.0), 20); + node1.setAttribute(column, map); + } else { + TimestampIntegerMap map = new TimestampIntegerMap(); + map.put(5.0, 10); + map.put(6.0, 20); + node1.setAttribute(column, map); + } + + // 5.0 is already in the map and stays at one reference, 8.0 adds one + setTimeAttribute(node1, column, timeRepresentation, 5.0, 30); + setTimeAttribute(node1, column, timeRepresentation, 8.0, 40); + + return graphStore; + } + + private static GraphStore roundTrip(GraphStore graphStore, TimeRepresentation timeRepresentation) throws IOException, ClassNotFoundException { + Serialization ser = new Serialization(graphStore.graphModel); + byte[] buf = ser.serialize(graphStore); + + GraphModelImpl readModel = new GraphModelImpl( + Configuration.builder().timeRepresentation(timeRepresentation).build()); + return (GraphStore) new Serialization(readModel).deserialize(buf); + } + + private static Object timeKey(TimeRepresentation timeRepresentation, double time) { + return timeRepresentation.equals(TimeRepresentation.INTERVAL) ? new Interval(time, time) : (Double) time; + } + + private static void addTime(ElementImpl element, TimeRepresentation timeRepresentation, double time) { + if (timeRepresentation.equals(TimeRepresentation.INTERVAL)) { + element.addInterval(new Interval(time, time)); + } else { + element.addTimestamp(time); + } + } + + private static void setTimeAttribute(ElementImpl element, Column column, TimeRepresentation timeRepresentation, double time, int value) { + if (timeRepresentation.equals(TimeRepresentation.INTERVAL)) { + element.setAttribute(column, value, new Interval(time, time)); + } else { + element.setAttribute(column, value, time); + } + } + + private static void removeTimeAttribute(ElementImpl element, Column column, TimeRepresentation timeRepresentation, double time) { + if (timeRepresentation.equals(TimeRepresentation.INTERVAL)) { + element.removeAttribute(column, new Interval(time, time)); + } else { + element.removeAttribute(column, time); + } + } + + private static void removeTime(ElementImpl element, TimeRepresentation timeRepresentation, double time) { + if (timeRepresentation.equals(TimeRepresentation.INTERVAL)) { + element.removeInterval(new Interval(time, time)); + } else { + element.removeTimestamp(time); + } + } + + @Test + public void testConfiguration() throws IOException, ClassNotFoundException { + GraphModelImpl graphModel = new GraphModelImpl(Configuration.builder().nodeIdType(Float.class) + .edgeIdType(Long.class).timeRepresentation(TimeRepresentation.INTERVAL).build()); Serialization ser = new Serialization(graphModel); - byte[] buf = ser.serialize(configuration); + byte[] buf = ser.serialize(graphModel.configuration); - graphModel = new GraphModelImpl(); - ser = new Serialization(graphModel); - Configuration l = (Configuration) ser.deserialize(buf); - Assert.assertTrue(configuration.equals(l)); + ser = new Serialization(new GraphModelImpl()); + ConfigurationImpl l = (ConfigurationImpl) ser.deserialize(buf); + Assert.assertTrue(graphModel.configuration.equals(l)); } @Test @@ -772,22 +1030,23 @@ public void testList() throws IOException, ClassNotFoundException { mixedList.add(42); Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(mixedList)), mixedList); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new IntArrayList(new int[] { 42 }))), new IntArrayList( - new int[] { 42 })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new FloatArrayList(new float[] { 42f }))), new FloatArrayList( - new float[] { 42f })); + Assert.assertEquals(new Serialization(null) + .deserialize(ser.serialize(new IntArrayList(new int[] { 42 }))), new IntArrayList(new int[] { 42 })); + Assert.assertEquals(new Serialization(null).deserialize(ser + .serialize(new FloatArrayList(new float[] { 42f }))), new FloatArrayList(new float[] { 42f })); Assert.assertEquals(new Serialization(null).deserialize(ser .serialize(new DoubleArrayList(new double[] { 42.0 }))), new DoubleArrayList(new double[] { 42.0 })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new ShortArrayList(new short[] { 42 }))), new ShortArrayList( - new short[] { 42 })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new ByteArrayList(new byte[] { 42 }))), new ByteArrayList( - new byte[] { 42 })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new LongArrayList(new long[] { 42l }))), new LongArrayList( - new long[] { 42l })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new BooleanArrayList( - new boolean[] { true }))), new BooleanArrayList(new boolean[] { true })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new CharArrayList(new char[] { 'a' }))), new CharArrayList( - new char[] { 'a' })); + Assert.assertEquals(new Serialization(null).deserialize(ser + .serialize(new ShortArrayList(new short[] { 42 }))), new ShortArrayList(new short[] { 42 })); + Assert.assertEquals(new Serialization(null).deserialize(ser + .serialize(new ByteArrayList(new byte[] { 42 }))), new ByteArrayList(new byte[] { 42 })); + Assert.assertEquals(new Serialization(null).deserialize(ser + .serialize(new LongArrayList(new long[] { 42l }))), new LongArrayList(new long[] { 42l })); + Assert.assertEquals(new Serialization(null) + .deserialize(ser.serialize(new BooleanArrayList(new boolean[] { true }))), new BooleanArrayList( + new boolean[] { true })); + Assert.assertEquals(new Serialization(null).deserialize(ser + .serialize(new CharArrayList(new char[] { 'a' }))), new CharArrayList(new char[] { 'a' })); } @Test @@ -810,22 +1069,24 @@ public void testSet() throws IOException, ClassNotFoundException { mixedSet.add(42); Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(mixedSet)), mixedSet); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new IntOpenHashSet(new int[] { 42 }))), new IntOpenHashSet( - new int[] { 42 })); + Assert.assertEquals(new Serialization(null).deserialize(ser + .serialize(new IntOpenHashSet(new int[] { 42 }))), new IntOpenHashSet(new int[] { 42 })); Assert.assertEquals(new Serialization(null).deserialize(ser .serialize(new FloatOpenHashSet(new float[] { 42f }))), new FloatOpenHashSet(new float[] { 42f })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new DoubleOpenHashSet( - new double[] { 42.0 }))), new DoubleOpenHashSet(new double[] { 42.0 })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new ShortOpenHashSet(new short[] { 42 }))), new ShortOpenHashSet( - new short[] { 42 })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new ByteOpenHashSet(new byte[] { 42 }))), new ByteOpenHashSet( - new byte[] { 42 })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new LongOpenHashSet(new long[] { 42l }))), new LongOpenHashSet( - new long[] { 42l })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new BooleanOpenHashSet( - new boolean[] { true }))), new BooleanOpenHashSet(new boolean[] { true })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new CharOpenHashSet(new char[] { 'a' }))), new CharOpenHashSet( - new char[] { 'a' })); + Assert.assertEquals(new Serialization(null) + .deserialize(ser.serialize(new DoubleOpenHashSet(new double[] { 42.0 }))), new DoubleOpenHashSet( + new double[] { 42.0 })); + Assert.assertEquals(new Serialization(null).deserialize(ser + .serialize(new ShortOpenHashSet(new short[] { 42 }))), new ShortOpenHashSet(new short[] { 42 })); + Assert.assertEquals(new Serialization(null).deserialize(ser + .serialize(new ByteOpenHashSet(new byte[] { 42 }))), new ByteOpenHashSet(new byte[] { 42 })); + Assert.assertEquals(new Serialization(null).deserialize(ser + .serialize(new LongOpenHashSet(new long[] { 42l }))), new LongOpenHashSet(new long[] { 42l })); + Assert.assertEquals(new Serialization(null) + .deserialize(ser.serialize(new BooleanOpenHashSet(new boolean[] { true }))), new BooleanOpenHashSet( + new boolean[] { true })); + Assert.assertEquals(new Serialization(null).deserialize(ser + .serialize(new CharOpenHashSet(new char[] { 'a' }))), new CharOpenHashSet(new char[] { 'a' })); } @Test @@ -848,27 +1109,33 @@ public void testMap() throws IOException, ClassNotFoundException { mixedMap.put(42, 42); Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(mixedMap)), mixedMap); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new Int2ObjectOpenHashMap( - new int[] { 42 }, new Object[] { "foo" }))), new Int2ObjectOpenHashMap(new int[] { 42 }, - new Object[] { "foo" })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new Float2ObjectOpenHashMap( - new float[] { 42f }, new Object[] { "foo" }))), new Float2ObjectOpenHashMap(new float[] { 42f }, - new Object[] { "foo" })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new Double2ObjectOpenHashMap( - new double[] { 42.0 }, new Object[] { "foo" }))), new Double2ObjectOpenHashMap(new double[] { 42.0 }, - new Object[] { "foo" })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new Short2ObjectOpenHashMap( - new short[] { 42 }, new Object[] { "foo" }))), new Short2ObjectOpenHashMap(new short[] { 42 }, - new Object[] { "foo" })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new Byte2ObjectOpenHashMap( - new byte[] { 42 }, new Object[] { "foo" }))), new Byte2ObjectOpenHashMap(new byte[] { 42 }, - new Object[] { "foo" })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new Long2ObjectOpenHashMap( - new long[] { 42l }, new Object[] { "foo" }))), new Long2ObjectOpenHashMap(new long[] { 42l }, - new Object[] { "foo" })); - Assert.assertEquals(new Serialization(null).deserialize(ser.serialize(new Char2ObjectOpenHashMap( - new char[] { 'a' }, new Object[] { "foo" }))), new Char2ObjectOpenHashMap(new char[] { 'a' }, - new Object[] { "foo" })); + Assert.assertEquals(new Serialization(null) + .deserialize(ser.serialize(new Int2ObjectOpenHashMap(new int[] { 42 }, + new Object[] { "foo" }))), new Int2ObjectOpenHashMap(new int[] { 42 }, new Object[] { "foo" })); + Assert.assertEquals(new Serialization(null) + .deserialize(ser.serialize(new Float2ObjectOpenHashMap(new float[] { 42f }, + new Object[] { "foo" }))), new Float2ObjectOpenHashMap(new float[] { 42f }, + new Object[] { "foo" })); + Assert.assertEquals(new Serialization(null) + .deserialize(ser.serialize(new Double2ObjectOpenHashMap(new double[] { 42.0 }, + new Object[] { "foo" }))), new Double2ObjectOpenHashMap(new double[] { 42.0 }, + new Object[] { "foo" })); + Assert.assertEquals(new Serialization(null) + .deserialize(ser.serialize(new Short2ObjectOpenHashMap(new short[] { 42 }, + new Object[] { "foo" }))), new Short2ObjectOpenHashMap(new short[] { 42 }, + new Object[] { "foo" })); + Assert.assertEquals(new Serialization(null) + .deserialize(ser.serialize(new Byte2ObjectOpenHashMap(new byte[] { 42 }, + new Object[] { "foo" }))), new Byte2ObjectOpenHashMap(new byte[] { 42 }, + new Object[] { "foo" })); + Assert.assertEquals(new Serialization(null) + .deserialize(ser.serialize(new Long2ObjectOpenHashMap(new long[] { 42l }, + new Object[] { "foo" }))), new Long2ObjectOpenHashMap(new long[] { 42l }, + new Object[] { "foo" })); + Assert.assertEquals(new Serialization(null) + .deserialize(ser.serialize(new Char2ObjectOpenHashMap(new char[] { 'a' }, + new Object[] { "foo" }))), new Char2ObjectOpenHashMap(new char[] { 'a' }, + new Object[] { "foo" })); } @Test @@ -981,7 +1248,10 @@ public void testFloat() throws IOException, ClassNotFoundException { @Test public void testChar() throws IOException, ClassNotFoundException { Serialization ser = new Serialization(null); - char[] vals = { 'a', ' ' }; + // Chars are written as a 2-byte short, so cover the boundaries of that + // range: above 0x7F, above 0x7FF, either side of the signed-short flip, + // the 16-bit maximum, and an unpaired surrogate + char[] vals = { 'a', ' ', '\u00e9', '\u4e2d', '\u7fff', '\u8000', '\uffff', '\ud83d' }; for (char i : vals) { byte[] buf = ser.serialize(i); Object l2 = ser.deserialize(buf); @@ -1126,7 +1396,7 @@ public void testByteArray() throws ClassNotFoundException, IOException { @Test public void testCharArray() throws ClassNotFoundException, IOException { Serialization ser = new Serialization(null); - char[] l = new char[] { '1', 'a', '&' }; + char[] l = new char[] { '1', 'a', '&', '\u00e9', '\u4e2d', '\u8000', '\uffff' }; Object deserialize = ser.deserialize(ser.serialize(l)); Assert.assertTrue(Arrays.equals(l, (char[]) deserialize)); } @@ -1166,14 +1436,6 @@ public void testBigInteger() throws IOException, ClassNotFoundException { Assert.assertEquals(d, ser.deserialize(ser.serialize(d))); } - @Test - public void testLocale() throws Exception { - Serialization ser = new Serialization(null); - Assert.assertEquals(Locale.FRANCE, ser.deserialize(ser.serialize(Locale.FRANCE))); - Assert.assertEquals(Locale.CANADA_FRENCH, ser.deserialize(ser.serialize(Locale.CANADA_FRENCH))); - Assert.assertEquals(Locale.SIMPLIFIED_CHINESE, ser.deserialize(ser.serialize(Locale.SIMPLIFIED_CHINESE))); - } - @Test public void testSmallGraphModel() throws Exception { GraphModelImpl gm = GraphGenerator.generateSmallGraphStore().graphModel; @@ -1200,6 +1462,38 @@ public void testSmallUndirectedGraphModel() throws Exception { Assert.assertTrue(read.deepEquals(gm)); } + @Test + public void testDefaultColumns() throws Exception { + GraphModelImpl gm = GraphGenerator.generateSmallUndirectedGraphStore().graphModel; + + Serialization ser = new Serialization(gm); + + DataInputOutput dio = new DataInputOutput(); + ser.serializeGraphModel(dio, gm); + byte[] bytes = dio.toByteArray(); + + GraphModelImpl read = ser.deserializeGraphModel(dio.reset(bytes)); + Assert.assertSame(read.defaultColumns().nodeId(), read.getNodeTable().getColumn("id")); + Assert.assertSame(read.defaultColumns().nodeLabel(), read.getNodeTable().getColumn("label")); + Assert.assertSame(read.defaultColumns().edgeId(), read.getEdgeTable().getColumn("id")); + Assert.assertSame(read.defaultColumns().edgeLabel(), read.getEdgeTable().getColumn("label")); + Assert.assertSame(read.defaultColumns().edgeWeight(), read.getEdgeTable().getColumn("weight")); + } + + @Test + public void testDeserializeWithGraphModel() throws Exception { + GraphModelImpl gm = GraphGenerator.generateSmallUndirectedGraphStore().graphModel; + Serialization ser = new Serialization(gm); + + DataInputOutput dio = new DataInputOutput(); + ser.serializeGraphModel(dio, gm); + byte[] bytes = dio.toByteArray(); + + GraphModelImpl read = GraphModel.Factory.newInstance(); + read = ser.deserializeGraphModel(dio.reset(bytes), read); + Assert.assertTrue(read.deepEquals(gm)); + } + @Test public void testDeserializeWithoutVersion() throws Exception { GraphModelImpl gm = GraphGenerator.generateSmallGraphStore().graphModel; @@ -1220,4 +1514,554 @@ public void serializeGraphModel(DataOutput out, GraphModelImpl model) throws IOE GraphModelImpl read = ser.deserializeGraphModelWithoutVersionPrefix(dio.reset(bytes), Serialization.VERSION); Assert.assertTrue(read.deepEquals(gm)); } + + @Test + public void testDeserializeGraphModelRejectsNewerVersion() throws Exception { + GraphModelImpl gm = GraphGenerator.generateSmallGraphStore().graphModel; + Serialization ser = new Serialization(gm) { + @Override + public void serializeGraphModel(DataOutput out, GraphModelImpl model) throws IOException { + this.model = model; + serialize(out, VERSION + 1f); + serialize(out, model.configuration); + serialize(out, model.store); + } + }; + + DataInputOutput dio = new DataInputOutput(); + ser.serializeGraphModel(dio, gm); + byte[] bytes = dio.toByteArray(); + + UnsupportedFormatVersionException ex = Assert + .expectThrows(UnsupportedFormatVersionException.class, () -> new Serialization() + .deserializeGraphModel(dio.reset(bytes))); + Assert.assertEquals(ex.getFileVersion(), Serialization.VERSION + 1f); + Assert.assertEquals(ex.getMaxSupportedVersion(), Serialization.VERSION); + } + + @Test + public void testDeserializeGraphModelWithoutVersionPrefixRejectsNewerVersion() throws Exception { + GraphModelImpl gm = GraphGenerator.generateSmallGraphStore().graphModel; + Serialization ser = new Serialization(gm) { + @Override + public void serializeGraphModel(DataOutput out, GraphModelImpl model) throws IOException { + this.model = model; + serialize(out, model.configuration); + serialize(out, model.store); + } + }; + + DataInputOutput dio = new DataInputOutput(); + ser.serializeGraphModel(dio, gm); + byte[] bytes = dio.toByteArray(); + + UnsupportedFormatVersionException ex = Assert + .expectThrows(UnsupportedFormatVersionException.class, () -> new Serialization() + .deserializeGraphModelWithoutVersionPrefix(dio.reset(bytes), Serialization.VERSION + 1f)); + Assert.assertEquals(ex.getFileVersion(), Serialization.VERSION + 1f); + Assert.assertEquals(ex.getMaxSupportedVersion(), Serialization.VERSION); + } + + @Test + public void testReuseSerializationInstanceForDeserialization() throws Exception { + GraphModelImpl gm1 = GraphGenerator.generateSmallGraphStore().graphModel; + GraphModelImpl gm2 = GraphGenerator.generateSmallMultiTypeGraphStore().graphModel; + + DataInputOutput dio1 = new DataInputOutput(); + new Serialization(gm1).serializeGraphModel(dio1, gm1); + byte[] bytes1 = dio1.toByteArray(); + + DataInputOutput dio2 = new DataInputOutput(); + new Serialization(gm2).serializeGraphModel(dio2, gm2); + byte[] bytes2 = dio2.toByteArray(); + + Serialization reused = new Serialization(); + GraphModelImpl read1 = reused.deserializeGraphModel(dio1.reset(bytes1)); + Assert.assertTrue(read1.deepEquals(gm1)); + + GraphModelImpl read2 = reused.deserializeGraphModel(dio2.reset(bytes2)); + Assert.assertTrue(read2.deepEquals(gm2)); + } + + @Test + public void testBitVectorEqual() throws Exception { + Serialization ser = new Serialization(); + + Random random = new Random(); + for (int i = 0; i < 20000; i += 97) { + BitSet bs = new BitSet(i); + + int p = random.nextInt(99) + 1; + for (int j = 0; j < i; j++) { + if (random.nextInt(100) < p) { + bs.set(j); + } + } + DataInputOutput dio = new DataInputOutput(); + ser.serializeBitSet(dio, bs); + byte[] bytes = dio.toByteArray(); + + BitSet deserializedBs = ser.deserializeBitSet(dio.reset(bytes)); + Assert.assertEquals(bs, deserializedBs); + } + } + + @Test + public void testSerializationTagsAreUnique() throws Exception { + // NULL_ID is excluded: it's an idMap sentinel (-1), not a wire tag + Map tagsByValue = new HashMap<>(); + for (Field field : Serialization.class.getDeclaredFields()) { + int modifiers = field.getModifiers(); + if (!Modifier.isStatic(modifiers) || !Modifier.isFinal(modifiers) || field.getType() != int.class) { + continue; + } + String name = field.getName(); + if (name.equals("NULL_ID")) { + continue; + } + field.setAccessible(true); + int value = field.getInt(null); + String previous = tagsByValue.put(value, name); + Assert.assertNull(previous, "Duplicate serialization tag " + value + " shared by " + previous + " and " + name); + } + } + + @Test(timeOut = 5000) + public void testSerializeGraphStoreHoldsReadLockForEntireDuration() throws Exception { + GraphModelImpl graphModel = new GraphModelImpl(); + GraphStore graphStore = graphModel.store; + NodeImpl[] nodes = GraphGenerator.generateSmallNodeList(); + graphStore.nodeStore.addAll(Arrays.asList(nodes)); + + CountDownLatch writeStarted = new CountDownLatch(1); + CountDownLatch proceed = new CountDownLatch(1); + BlockingDataOutput blockingOutput = new BlockingDataOutput(writeStarted, proceed); + + Serialization ser = new Serialization(graphModel); + AtomicReference writerError = new AtomicReference<>(); + Thread writer = new Thread(() -> { + try { + ser.serializeGraphStore(blockingOutput, graphStore); + } catch (Exception e) { + writerError.set(e); + } + }); + writer.start(); + + Assert.assertTrue(writeStarted.await(2, TimeUnit.SECONDS), "serialization should have started writing"); + + AtomicBoolean writeLockAcquired = new AtomicBoolean(false); + Thread locker = new Thread(() -> { + graphStore.writeLock(); + writeLockAcquired.set(true); + graphStore.writeUnlock(); + }); + locker.start(); + + // Give the locker thread a chance to attempt (and be blocked by) the write lock + Thread.sleep(300); + Assert.assertFalse(writeLockAcquired + .get(), "write lock must not be acquired while serializeGraphStore still holds the read lock"); + + proceed.countDown(); + writer.join(2000); + locker.join(2000); + + Assert.assertNull(writerError.get()); + Assert.assertTrue(writeLockAcquired.get(), "write lock should be acquired after serialization completes"); + } + + /** + * A DataOutput that blocks on its very first write call until released, so tests can deterministically assert what + * lock state holds while a serialization call is in progress. + */ + private static class BlockingDataOutput implements DataOutput { + + private final DataOutputStream delegate = new DataOutputStream(new ByteArrayOutputStream()); + private final CountDownLatch started; + private final CountDownLatch proceed; + private final AtomicBoolean first = new AtomicBoolean(true); + + private BlockingDataOutput(CountDownLatch started, CountDownLatch proceed) { + this.started = started; + this.proceed = proceed; + } + + private void beforeWrite() throws IOException { + if (first.compareAndSet(true, false)) { + started.countDown(); + try { + proceed.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException(e); + } + } + } + + @Override + public void write(int b) throws IOException { + beforeWrite(); + delegate.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + beforeWrite(); + delegate.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + beforeWrite(); + delegate.write(b, off, len); + } + + @Override + public void writeBoolean(boolean v) throws IOException { + beforeWrite(); + delegate.writeBoolean(v); + } + + @Override + public void writeByte(int v) throws IOException { + beforeWrite(); + delegate.writeByte(v); + } + + @Override + public void writeShort(int v) throws IOException { + beforeWrite(); + delegate.writeShort(v); + } + + @Override + public void writeChar(int v) throws IOException { + beforeWrite(); + delegate.writeChar(v); + } + + @Override + public void writeInt(int v) throws IOException { + beforeWrite(); + delegate.writeInt(v); + } + + @Override + public void writeLong(long v) throws IOException { + beforeWrite(); + delegate.writeLong(v); + } + + @Override + public void writeFloat(float v) throws IOException { + beforeWrite(); + delegate.writeFloat(v); + } + + @Override + public void writeDouble(double v) throws IOException { + beforeWrite(); + delegate.writeDouble(v); + } + + @Override + public void writeBytes(String s) throws IOException { + beforeWrite(); + delegate.writeBytes(s); + } + + @Override + public void writeChars(String s) throws IOException { + beforeWrite(); + delegate.writeChars(s); + } + + @Override + public void writeUTF(String s) throws IOException { + beforeWrite(); + delegate.writeUTF(s); + } + } + + @Test(timeOut = 5000) + public void testDeserializeGraphStoreHoldsWriteLockForEntireDuration() throws Exception { + GraphModelImpl sourceModel = new GraphModelImpl(); + GraphStore sourceStore = sourceModel.store; + NodeImpl[] nodes = GraphGenerator.generateSmallNodeList(); + sourceStore.nodeStore.addAll(Arrays.asList(nodes)); + DataInputOutput dio = new DataInputOutput(); + new Serialization(sourceModel).serializeGraphStore(dio, sourceStore); + byte[] bytes = dio.toByteArray(); + + GraphModelImpl targetModel = new GraphModelImpl(); + GraphStore targetStore = targetModel.store; + + CountDownLatch readStarted = new CountDownLatch(1); + CountDownLatch proceed = new CountDownLatch(1); + BlockingDataInput blockingInput = new BlockingDataInput(bytes, readStarted, proceed); + + Serialization ser = new Serialization(targetModel); + AtomicReference readerError = new AtomicReference<>(); + Thread reader = new Thread(() -> { + try { + ser.deserializeGraphStore(blockingInput); + } catch (Exception e) { + readerError.set(e); + } + }); + reader.start(); + + Assert.assertTrue(readStarted.await(2, TimeUnit.SECONDS), "deserialization should have started reading"); + + AtomicBoolean readLockAcquired = new AtomicBoolean(false); + Thread locker = new Thread(() -> { + targetStore.readLock(); + readLockAcquired.set(true); + targetStore.readUnlock(); + }); + locker.start(); + + // Give the locker thread a chance to attempt (and be blocked by) the read lock + Thread.sleep(300); + Assert.assertFalse(readLockAcquired + .get(), "read lock must not be acquired while deserializeGraphStore still holds the write lock"); + + proceed.countDown(); + reader.join(2000); + locker.join(2000); + + Assert.assertNull(readerError.get()); + Assert.assertTrue(readLockAcquired.get(), "read lock should be acquired after deserialization completes"); + } + + /** + * A DataInput that blocks on its very first read call until released, so tests can deterministically assert what + * lock state holds while a deserialization call is in progress. + */ + private static class BlockingDataInput implements DataInput { + + private final DataInputStream delegate; + private final CountDownLatch started; + private final CountDownLatch proceed; + private final AtomicBoolean first = new AtomicBoolean(true); + + private BlockingDataInput(byte[] bytes, CountDownLatch started, CountDownLatch proceed) { + this.delegate = new DataInputStream(new ByteArrayInputStream(bytes)); + this.started = started; + this.proceed = proceed; + } + + private void beforeRead() throws IOException { + if (first.compareAndSet(true, false)) { + started.countDown(); + try { + proceed.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException(e); + } + } + } + + @Override + public void readFully(byte[] b) throws IOException { + beforeRead(); + delegate.readFully(b); + } + + @Override + public void readFully(byte[] b, int off, int len) throws IOException { + beforeRead(); + delegate.readFully(b, off, len); + } + + @Override + public int skipBytes(int n) throws IOException { + beforeRead(); + return delegate.skipBytes(n); + } + + @Override + public boolean readBoolean() throws IOException { + beforeRead(); + return delegate.readBoolean(); + } + + @Override + public byte readByte() throws IOException { + beforeRead(); + return delegate.readByte(); + } + + @Override + public int readUnsignedByte() throws IOException { + beforeRead(); + return delegate.readUnsignedByte(); + } + + @Override + public short readShort() throws IOException { + beforeRead(); + return delegate.readShort(); + } + + @Override + public int readUnsignedShort() throws IOException { + beforeRead(); + return delegate.readUnsignedShort(); + } + + @Override + public char readChar() throws IOException { + beforeRead(); + return delegate.readChar(); + } + + @Override + public int readInt() throws IOException { + beforeRead(); + return delegate.readInt(); + } + + @Override + public long readLong() throws IOException { + beforeRead(); + return delegate.readLong(); + } + + @Override + public float readFloat() throws IOException { + beforeRead(); + return delegate.readFloat(); + } + + @Override + public double readDouble() throws IOException { + beforeRead(); + return delegate.readDouble(); + } + + @Override + public String readLine() throws IOException { + beforeRead(); + return delegate.readLine(); + } + + @Override + public String readUTF() throws IOException { + beforeRead(); + return delegate.readUTF(); + } + } + + private GraphStore buildMultiBlockGraphWithGarbage() { + GraphStore graphStore = GraphGenerator.generateLargeGraphStore(); + + // Delete a scattered subset of nodes/edges so garbage (removed) slots fall across multiple + // blocks, exercising the same "skip garbage slots" behavior in the block-splitting spliterator + // that the plain sequential iterators already rely on. + NodeImpl[] nodes = graphStore.nodeStore.toArray(); + for (int i = 0; i < nodes.length; i += 11) { + graphStore.removeNode(nodes[i]); + } + EdgeImpl[] edges = graphStore.edgeStore.toArray(); + for (int i = 0; i < edges.length; i += 5) { + graphStore.removeEdge(edges[i]); + } + return graphStore; + } + + @Test + public void testParallelAndSequentialNodeEdgeEncodingProduceIdenticalBytes() throws Exception { + GraphStore graphStore = buildMultiBlockGraphWithGarbage(); + Serialization ser = new Serialization(); + + List> nodeChunks = Serialization.splitIntoBlockChunks(graphStore.nodeStore.spliterator()); + List> edgeChunks = Serialization.splitIntoBlockChunks(graphStore.edgeStore.spliterator()); + Assert.assertTrue(nodeChunks.size() > 1 || edgeChunks + .size() > 1, "test graph should span more than one block to be meaningful"); + + DataInputOutput sequentialOut = new DataInputOutput(); + ser.serializeChunksSequentially(sequentialOut, Serialization.splitIntoBlockChunks(graphStore.nodeStore + .spliterator()), Serialization.splitIntoBlockChunks(graphStore.edgeStore.spliterator())); + + DataInputOutput parallelOut = new DataInputOutput(); + ser.serializeChunksInParallel(parallelOut, Serialization.splitIntoBlockChunks(graphStore.nodeStore + .spliterator()), Serialization.splitIntoBlockChunks(graphStore.edgeStore.spliterator())); + + Assert.assertEquals(Arrays.copyOf(parallelOut.getBuf(), parallelOut.getPos()), Arrays + .copyOf(sequentialOut.getBuf(), sequentialOut.getPos())); + } + + @Test + public void testSerializeAndDeserializeMultiBlockGraphRoundTrips() throws Exception { + GraphStore graphStore = GraphGenerator.generateLargeGraphStore(); + Assert.assertTrue(Serialization.splitIntoBlockChunks(graphStore.nodeStore.spliterator()) + .size() > 1, "test graph should span more than one block to exercise the parallel path"); + + GraphModelImpl graphModel = new GraphModelImpl(); + Serialization ser = new Serialization(graphModel); + DataInputOutput dio = new DataInputOutput(); + ser.serializeGraphStore(dio, graphStore); + byte[] bytes = dio.toByteArray(); + + GraphModelImpl graphModel2 = new GraphModelImpl(); + Serialization ser2 = new Serialization(graphModel2); + GraphStore deserialized = ser2.deserializeGraphStore(dio.reset(bytes)); + + Assert.assertTrue(graphStore.nodeStore.deepEquals(deserialized.nodeStore)); + Assert.assertTrue(graphStore.edgeStore.deepEquals(deserialized.edgeStore)); + } + + @Test(timeOut = 5000) + public void testSerializeChunksInParallelPropagatesExceptionAndLeavesNoThreads() throws Exception { + Serialization ser = new Serialization(); + List> nodeChunks = new ArrayList<>(); + nodeChunks.add(new ThrowingSpliterator<>()); + nodeChunks.add(new ThrowingSpliterator<>()); + List> edgeChunks = new ArrayList<>(); + + Assert.expectThrows(RuntimeException.class, () -> ser + .serializeChunksInParallel(new DataInputOutput(), nodeChunks, edgeChunks)); + + long deadline = System.currentTimeMillis() + 2000; + long remaining; + do { + remaining = countGraphstoreSerializeThreads(); + if (remaining == 0) { + break; + } + Thread.sleep(50); + } while (System.currentTimeMillis() < deadline); + Assert.assertEquals(remaining, 0, "no graphstore-serialize threads should remain after a failed parallel encode"); + } + + private static long countGraphstoreSerializeThreads() { + return Thread.getAllStackTraces().keySet().stream().filter(t -> "graphstore-serialize".equals(t.getName())) + .count(); + } + + private static final class ThrowingSpliterator implements Spliterator { + + @Override + public boolean tryAdvance(Consumer action) { + throw new RuntimeException("boom"); + } + + @Override + public Spliterator trySplit() { + return null; + } + + @Override + public long estimateSize() { + return 1; + } + + @Override + public int characteristics() { + return 0; + } + } } diff --git a/src/test/java/org/gephi/graph/impl/SpatialIndexImplTest.java b/src/test/java/org/gephi/graph/impl/SpatialIndexImplTest.java new file mode 100644 index 00000000..f2010473 --- /dev/null +++ b/src/test/java/org/gephi/graph/impl/SpatialIndexImplTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2012-2013 Gephi Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.gephi.graph.impl; + +import java.util.Arrays; +import org.gephi.graph.api.Configuration; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.EdgeIterable; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.NodeIterable; +import org.gephi.graph.api.Rect2D; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class SpatialIndexImplTest { + + private static final float BOUNDS = 1000f; + private static final Rect2D BOUNDS_RECT = new Rect2D(-BOUNDS, -BOUNDS, BOUNDS, BOUNDS); + + @Test + public void testDisabled() { + GraphStore store = new GraphStore(null, Configuration.builder().enableSpatialIndex(false).build()); + Assert.assertNull(store.spatialIndex); + } + + @Test + public void testGetEdgesEmpty() { + SpatialIndexImpl spatialIndex = new GraphStore(null, getConfig()).spatialIndex; + Assert.assertTrue(spatialIndex.getEdgesInArea(BOUNDS_RECT).toCollection().isEmpty()); + } + + @Test + public void testGetElementsBothNodesVisible() { + GraphStore store = GraphGenerator.generateTinyGraphStore(getConfig()); + + NodeImpl n1 = store.getNode("1"); + NodeImpl n2 = store.getNode("2"); + EdgeImpl e = store.getEdge("0"); + + SpatialIndexImpl spatialIndex = store.spatialIndex; + assertSame(spatialIndex.getNodesInArea(BOUNDS_RECT), n1, n2); + assertSame(spatialIndex.getEdgesInArea(BOUNDS_RECT), e, e); + } + + @Test + public void testGetElementsOneNodeVisible() { + GraphStore store = GraphGenerator.generateTinyGraphStore(getConfig()); + + NodeImpl n1 = store.getNode("1"); + n1.setPosition(300000f, 300000f); + NodeImpl n2 = store.getNode("2"); + EdgeImpl e = store.getEdge("0"); + + SpatialIndexImpl spatialIndex = store.spatialIndex; + assertSame(spatialIndex.getNodesInArea(BOUNDS_RECT), n2); + assertSame(spatialIndex.getEdgesInArea(BOUNDS_RECT), e); + } + + @Test + public void testGetElementsWithoutNodeVisible() { + GraphStore store = GraphGenerator.generateTinyGraphStore(getConfig()); + + NodeImpl n1 = store.getNode("1"); + n1.setPosition(300000f, 300000f); + NodeImpl n2 = store.getNode("2"); + n2.setPosition(300001f, 300001f); + EdgeImpl e = store.getEdge("0"); + + SpatialIndexImpl spatialIndex = store.spatialIndex; + Assert.assertTrue(spatialIndex.getNodesInArea(BOUNDS_RECT).toCollection().isEmpty()); + Assert.assertTrue(spatialIndex.getEdgesInArea(BOUNDS_RECT).toCollection().isEmpty()); + } + + @Test + public void testGetElementsWithSelfLoop() { + GraphStore store = GraphGenerator.generateTinyGraphStoreWithSelfLoop(getConfig()); + + NodeImpl n1 = store.getNode("1"); + EdgeImpl e = store.getEdge("0"); + + SpatialIndexImpl spatialIndex = store.spatialIndex; + assertSame(spatialIndex.getNodesInArea(BOUNDS_RECT), n1); + assertSame(spatialIndex.getEdgesInArea(BOUNDS_RECT), e); + } + + @Test + public void testClear() { + GraphStore store = GraphGenerator.generateTinyGraphStore(getConfig()); + + SpatialIndexImpl spatialIndex = store.spatialIndex; + Assert.assertEquals(spatialIndex.getObjectCount(), store.getNodeCount()); + Assert.assertEquals(spatialIndex.getNodesInArea(new Rect2D(-1, -1, 1, 1)).toCollection().size(), store + .getNodeCount()); + store.clear(); + Assert.assertEquals(spatialIndex.getObjectCount(), 0); + Assert.assertTrue(spatialIndex.getNodesInArea(new Rect2D(-1, -1, 1, 1)).toCollection().isEmpty()); + } + + private void assertSame(NodeIterable iterable, Node... expected) { + Assert.assertEquals(iterable.toCollection(), Arrays.asList(expected)); + } + + private void assertSame(EdgeIterable iterable, Edge... expected) { + Assert.assertEquals(iterable.toCollection(), Arrays.asList(expected)); + } + + // Configuration with spatial index + private Configuration getConfig() { + return Configuration.builder().enableSpatialIndex(true).build(); + } +} diff --git a/store/src/test/java/org/gephi/graph/impl/TableImplTest.java b/src/test/java/org/gephi/graph/impl/TableImplTest.java similarity index 67% rename from store/src/test/java/org/gephi/graph/impl/TableImplTest.java rename to src/test/java/org/gephi/graph/impl/TableImplTest.java index d71c87d0..5b7a1da4 100644 --- a/store/src/test/java/org/gephi/graph/impl/TableImplTest.java +++ b/src/test/java/org/gephi/graph/impl/TableImplTest.java @@ -16,10 +16,12 @@ package org.gephi.graph.impl; import java.awt.Color; +import java.time.Instant; import java.util.Arrays; import org.gephi.graph.api.Column; import org.gephi.graph.api.Origin; import org.gephi.graph.api.Node; +import org.gephi.graph.api.types.TimestampIntegerMap; import org.testng.Assert; import org.testng.annotations.Test; @@ -27,23 +29,26 @@ public class TableImplTest { @Test public void testTable() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Assert.assertEquals(table.countColumns(), 0); + Assert.assertEquals(table.size(), 0); + Assert.assertTrue(table.isEmpty()); } @Test public void testAddColumnDefault() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Column col = table.addColumn("Id", Integer.class); Assert.assertEquals(table.countColumns(), 1); Assert.assertEquals(table.getColumn("Id"), col); Assert.assertEquals(table.getColumn("id"), col); + Assert.assertTrue(col.exists()); } @Test public void testAddColumnWithOrigin() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Column col = table.addColumn("Id", Integer.class, Origin.PROPERTY); Assert.assertEquals(col.getOrigin(), Origin.PROPERTY); Assert.assertTrue(col.isProperty()); @@ -51,7 +56,7 @@ public void testAddColumnWithOrigin() { @Test public void testAddColumnWithTitleAndDefaultValue() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Column col = table.addColumn("Id", "Foo", Integer.class, 42); Assert.assertEquals(col.getTitle(), "Foo"); Assert.assertEquals(col.getDefaultValue(), 42); @@ -59,13 +64,13 @@ public void testAddColumnWithTitleAndDefaultValue() { @Test(expectedExceptions = IllegalArgumentException.class) public void testUnknownType() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); table.addColumn("Id", Node.class); } @Test(expectedExceptions = IllegalArgumentException.class) public void testDefaultValueWrongType() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Float defaultValue = 25f; table.addColumn("Id", null, Integer.class, Origin.DATA, defaultValue, false); @@ -73,45 +78,50 @@ public void testDefaultValueWrongType() { @Test(expectedExceptions = NullPointerException.class) public void testOriginCantBeNull() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); table.addColumn("Id", null, Integer.class, null, 0, false); } @Test(expectedExceptions = NullPointerException.class) public void testOriginCantBeNull2() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); table.addColumn("Id", Integer.class, null); } @Test(expectedExceptions = NullPointerException.class) public void testTypeClassCantBeNull() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); table.addColumn("Id", null, null, Origin.DATA, 0, false); } @Test(expectedExceptions = NullPointerException.class) public void testTypeClassCantBeNull2() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); table.addColumn("Id", null); } @Test public void testIsIndexed() { - TableImpl table = new TableImpl<>(Node.class, true); + GraphStore graphStore = new GraphStore(); + TableImpl table = new TableImpl<>(graphStore, Node.class); Column col1 = table.addColumn("Id", null, Integer.class, Origin.DATA, null, false); Column col2 = table.addColumn("1", null, Integer.class, Origin.DATA, null, true); + Column col3 = table.addColumn("2", null, TimestampIntegerMap.class, Origin.DATA, null, true); + Column col4 = table.addColumn("3", null, Integer[].class, Origin.DATA, null, true); Assert.assertFalse(col1.isIndexed()); Assert.assertTrue(col2.isIndexed()); + Assert.assertTrue(col3.isIndexed()); + Assert.assertTrue(col4.isIndexed()); } @Test public void testGetColumnId() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Column col = table.addColumn("Id", Integer.class); Column c1 = table.getColumn("Id"); @@ -122,13 +132,13 @@ public void testGetColumnId() { @Test public void testGetColumnBadId() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Assert.assertNull(table.getColumn("Id")); } @Test public void testGetColumnIndex() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Column col = table.addColumn("Id", Integer.class); Column c = table.getColumn(0); @@ -137,7 +147,7 @@ public void testGetColumnIndex() { @Test public void testHasColumn() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); table.addColumn("Id", Integer.class); Assert.assertTrue(table.hasColumn("Id")); @@ -146,66 +156,79 @@ public void testHasColumn() { Assert.assertTrue(table.hasColumn("iD")); } + @Test + public void testContains() { + TableImpl table = new TableImpl<>(Node.class); + Column col = table.addColumn("Id", Integer.class); + + Assert.assertTrue(table.contains(col)); + } + @Test(expectedExceptions = IllegalArgumentException.class) public void testGetColumnBadIndex() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); table.getColumn(0); } @Test public void testTitleBackFill() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Column col = table.addColumn("Id", Integer.class); Assert.assertEquals(col.getTitle(), "Id"); } @Test public void testIdLowercase() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Column col = table.addColumn("A", Integer.class); Assert.assertEquals(col.getId(), "a"); } @Test(expectedExceptions = IllegalArgumentException.class) public void testNonStandardType() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); table.addColumn("Id", Color.class); } @Test public void testStandardizePrimitiveType() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Column col = table.addColumn("Id", int.class); Assert.assertEquals(col.getTypeClass(), Integer.class); } @Test public void testStandardizeArrayType() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Column col = table.addColumn("test_col"/* - * Id does not allow non-simple - * types + * Id does not allow non-simple types */, Integer[].class); Assert.assertEquals(col.getTypeClass(), int[].class); } @Test public void testStandardizeArrayDefaultValue() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Integer[] t = new Integer[] { 1, 2 }; Column col = table.addColumn("test_col"/* - * Id does not allow non-simple - * types + * Id does not allow non-simple types */, null, Integer[].class, Origin.DATA, t, false); Object d = col.getDefaultValue(); Assert.assertEquals(d.getClass(), int[].class); Assert.assertEquals(d, new int[] { 1, 2 }); } + @Test + public void testInstantColumn() { + TableImpl table = new TableImpl<>(Node.class); + Column col = table.addColumn("test_col", Instant.class); + Assert.assertEquals(col.getTypeClass(), Instant.class); + } + @Test public void testRemoveColumn() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Column col = table.addColumn("Id", Integer.class); table.removeColumn(col); @@ -213,9 +236,18 @@ public void testRemoveColumn() { Assert.assertFalse(table.hasColumn("id")); } + @Test + public void testRemove() { + TableImpl table = new TableImpl<>(Node.class); + Column col = table.addColumn("Id", Integer.class); + + table.remove(col); + Assert.assertFalse(table.contains(col)); + } + @Test public void testRemoveColumnString() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); table.addColumn("Id", Integer.class); table.removeColumn("Id"); @@ -229,7 +261,7 @@ public void testRemoveColumnString() { @Test public void testCountColumns() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); table.addColumn("Id", Integer.class); table.removeColumn("Id"); @@ -239,30 +271,37 @@ public void testCountColumns() { @Test public void testGetElementClass() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Assert.assertEquals(table.getElementClass(), Node.class); } @Test public void testToArray() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Column col = table.addColumn("Id", Integer.class); Assert.assertEquals(table.toArray(), new Column[] { col }); } + @Test + public void testToArrayFromCollection() { + TableImpl table = new TableImpl<>(Node.class); + Column col = table.addColumn("Id", Integer.class); + Assert.assertEquals(table.toArray(new Column[0]), new Column[] { col }); + } + @Test public void testToList() { - TableImpl table = new TableImpl<>(Node.class, false); + TableImpl table = new TableImpl<>(Node.class); Column col = table.addColumn("Id", Integer.class); Assert.assertEquals(table.toList(), Arrays.asList(new Column[] { col })); } @Test public void testDeepEquals() { - TableImpl table1 = new TableImpl<>(Node.class, false); + TableImpl table1 = new TableImpl<>(Node.class); table1.addColumn("Id", Integer.class); - TableImpl table2 = new TableImpl<>(Node.class, false); + TableImpl table2 = new TableImpl<>(Node.class); table2.addColumn("Id", Integer.class); Assert.assertTrue(table1.deepEquals(table2)); @@ -270,10 +309,10 @@ public void testDeepEquals() { @Test public void testDeepHashCode() { - TableImpl table1 = new TableImpl<>(Node.class, false); + TableImpl table1 = new TableImpl<>(Node.class); table1.addColumn("Id", Integer.class); - TableImpl table2 = new TableImpl<>(Node.class, false); + TableImpl table2 = new TableImpl<>(Node.class); table2.addColumn("Id", Integer.class); Assert.assertEquals(table1.deepHashCode(), table2.deepHashCode()); diff --git a/store/src/test/java/org/gephi/graph/impl/TableObserverTest.java b/src/test/java/org/gephi/graph/impl/TableObserverTest.java similarity index 89% rename from store/src/test/java/org/gephi/graph/impl/TableObserverTest.java rename to src/test/java/org/gephi/graph/impl/TableObserverTest.java index fed5174d..7f38be02 100644 --- a/store/src/test/java/org/gephi/graph/impl/TableObserverTest.java +++ b/src/test/java/org/gephi/graph/impl/TableObserverTest.java @@ -28,7 +28,7 @@ public class TableObserverTest { @Test public void testDefaultObserver() { - TableImpl table = new TableImpl(Node.class, false); + TableImpl table = new TableImpl(Node.class); TableObserverImpl tableObserver = (TableObserverImpl) table.createTableObserver(false); Assert.assertFalse(tableObserver.destroyed); @@ -40,7 +40,7 @@ public void testDefaultObserver() { @Test public void testObserverAddColumn() { - TableImpl table = new TableImpl(Node.class, false); + TableImpl table = new TableImpl(Node.class); TableObserverImpl tableObserver = (TableObserverImpl) table.createTableObserver(false); table.addColumn("0", Integer.class); @@ -51,7 +51,7 @@ public void testObserverAddColumn() { @Test public void testObserverRemoveColumn() { - TableImpl table = new TableImpl(Node.class, false); + TableImpl table = new TableImpl(Node.class); table.addColumn("0", Integer.class); TableObserverImpl tableObserver = (TableObserverImpl) table.createTableObserver(false); table.removeColumn("0"); @@ -62,7 +62,7 @@ public void testObserverRemoveColumn() { @Test public void testObserverModifyColumn() { - TableImpl table = new TableImpl(Node.class, false); + TableImpl table = new TableImpl(Node.class); Column col = table.addColumn("0", TimestampIntegerMap.class); TableObserverImpl tableObserver = (TableObserverImpl) table.createTableObserver(false); col.setEstimator(Estimator.MAX); @@ -73,7 +73,7 @@ public void testObserverModifyColumn() { @Test public void testDestroyObserver() { - TableImpl table = new TableImpl(Node.class, false); + TableImpl table = new TableImpl(Node.class); TableObserverImpl tableObserver = (TableObserverImpl) table.createTableObserver(false); tableObserver.destroy(); @@ -84,21 +84,21 @@ public void testDestroyObserver() { @Test(expectedExceptions = RuntimeException.class) public void testGetDiffWithoutSetting() { - TableImpl table = new TableImpl(Node.class, false); + TableImpl table = new TableImpl(Node.class); TableObserverImpl tableObserver = (TableObserverImpl) table.createTableObserver(false); tableObserver.getDiff(); } @Test(expectedExceptions = IllegalStateException.class) public void testGetDiffWithoutHasGraphChanged() { - TableImpl table = new TableImpl(Node.class, false); + TableImpl table = new TableImpl(Node.class); TableObserverImpl tableObserver = (TableObserverImpl) table.createTableObserver(true); tableObserver.getDiff(); } @Test public void testDiffRemoveColumn() { - TableImpl table = new TableImpl(Node.class, false); + TableImpl table = new TableImpl(Node.class); table.addColumn("0", Integer.class); Column[] columns = table.toArray(); TableObserverImpl tableObserver = (TableObserverImpl) table.createTableObserver(true); @@ -115,7 +115,7 @@ public void testDiffRemoveColumn() { @Test public void testDiffAddColumn() { - TableImpl table = new TableImpl(Node.class, false); + TableImpl table = new TableImpl(Node.class); TableObserverImpl tableObserver = (TableObserverImpl) table.createTableObserver(true); table.addColumn("0", Integer.class); Column[] columns = table.toArray(); @@ -131,7 +131,7 @@ public void testDiffAddColumn() { @Test public void testDiffModifyColumn() { - TableImpl table = new TableImpl(Node.class, false); + TableImpl table = new TableImpl(Node.class); Column col = table.addColumn("0", TimestampIntegerMap.class); TableObserverImpl tableObserver = (TableObserverImpl) table.createTableObserver(true); col.setEstimator(Estimator.AVERAGE); diff --git a/store/src/test/java/org/gephi/graph/impl/TimeStoreTest.java b/src/test/java/org/gephi/graph/impl/TimeStoreTest.java similarity index 72% rename from store/src/test/java/org/gephi/graph/impl/TimeStoreTest.java rename to src/test/java/org/gephi/graph/impl/TimeStoreTest.java index 07d2abe9..cf495651 100644 --- a/store/src/test/java/org/gephi/graph/impl/TimeStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/TimeStoreTest.java @@ -26,14 +26,14 @@ public class TimeStoreTest { @Test public void testEmpty() { - TimeStore store = new TimeStore(null, null, false); + TimeStore store = new TimeStore(null, false); Assert.assertTrue(store.isEmpty()); } @Test public void testDeepEqualsEmpty() { - TimeStore store1 = new TimeStore(null, null, false); - TimeStore store2 = new TimeStore(null, null, false); + TimeStore store1 = new TimeStore(null, false); + TimeStore store2 = new TimeStore(null, false); Assert.assertTrue(store1.deepEquals(store2)); Assert.assertEquals(store1.deepHashCode(), store2.deepHashCode()); @@ -42,21 +42,35 @@ public void testDeepEqualsEmpty() { @Test public void testGetMinNull() { GraphStore graphStore = new GraphStore(); - TimeStore store = new TimeStore(graphStore, null, true); + TimeStore store = new TimeStore(graphStore, true); Assert.assertEquals(store.getMin(graphStore), Double.NEGATIVE_INFINITY); } @Test public void testGetMaxNull() { GraphStore graphStore = new GraphStore(); - TimeStore store = new TimeStore(graphStore, null, true); + TimeStore store = new TimeStore(graphStore, true); + Assert.assertEquals(store.getMax(graphStore), Double.POSITIVE_INFINITY); + } + + @Test + public void testGetMinNotIndexed() { + GraphStore graphStore = new GraphStore(); + TimeStore store = new TimeStore(graphStore, false); + Assert.assertEquals(store.getMin(graphStore), Double.NEGATIVE_INFINITY); + } + + @Test + public void testGetMaxNotIndexed() { + GraphStore graphStore = new GraphStore(); + TimeStore store = new TimeStore(graphStore, false); Assert.assertEquals(store.getMax(graphStore), Double.POSITIVE_INFINITY); } @Test public void testGetMin() { GraphStore graphStore = new GraphStore(); - TimeStore store = new TimeStore(graphStore, null, true); + TimeStore store = new TimeStore(graphStore, true); store.nodeIndexStore.add(1.0); store.nodeIndexStore.add(2.0); Assert.assertEquals(store.getMin(graphStore), 1.0); @@ -65,7 +79,7 @@ public void testGetMin() { @Test public void testGetMax() { GraphStore graphStore = new GraphStore(); - TimeStore store = new TimeStore(graphStore, null, true); + TimeStore store = new TimeStore(graphStore, true); store.nodeIndexStore.add(1.0); store.nodeIndexStore.add(2.0); Assert.assertEquals(store.getMax(graphStore), 2.0); @@ -73,7 +87,7 @@ public void testGetMax() { @Test public void testClearEdges() { - TimeStore store = new TimeStore(null, null, true); + TimeStore store = new TimeStore(null, true); store.nodeIndexStore.add(1.0); store.edgeIndexStore.add(2.0); store.clearEdges(); @@ -84,7 +98,7 @@ public void testClearEdges() { @Test public void testClear() { - TimeStore store = new TimeStore(null, null, true); + TimeStore store = new TimeStore(null, true); store.nodeIndexStore.add(1.0); store.edgeIndexStore.add(2.0); store.clear(); @@ -95,8 +109,8 @@ public void testClear() { @Test public void testDeepEquals() { - TimeStore store1 = new TimeStore(null, null, true); - TimeStore store2 = new TimeStore(null, null, true); + TimeStore store1 = new TimeStore(null, true); + TimeStore store2 = new TimeStore(null, true); store1.nodeIndexStore.add(1.0); store1.edgeIndexStore.add(2.0); store2.nodeIndexStore.add(1.0); diff --git a/store/src/test/java/org/gephi/graph/impl/TimestampIndexImplTest.java b/src/test/java/org/gephi/graph/impl/TimestampIndexImplTest.java similarity index 93% rename from store/src/test/java/org/gephi/graph/impl/TimestampIndexImplTest.java rename to src/test/java/org/gephi/graph/impl/TimestampIndexImplTest.java index 8dd1e0c3..86e58b31 100644 --- a/store/src/test/java/org/gephi/graph/impl/TimestampIndexImplTest.java +++ b/src/test/java/org/gephi/graph/impl/TimestampIndexImplTest.java @@ -29,7 +29,7 @@ public class TimestampIndexImplTest { @Test public void testGetMin() { - TimeStore timestampStore = new TimeStore(null, null, true); + TimeStore timestampStore = new TimeStore(null, true); TimestampIndexStore store = (TimestampIndexStore) timestampStore.nodeIndexStore; Assert.assertEquals(store.mainIndex.getMinTimestamp(), Double.NEGATIVE_INFINITY); @@ -70,7 +70,7 @@ public void testGetMinMaxWithView() { @Test public void testGetMax() { - TimeStore timestampStore = new TimeStore(null, null, true); + TimeStore timestampStore = new TimeStore(null, true); TimestampIndexStore store = (TimestampIndexStore) timestampStore.nodeIndexStore; Assert.assertEquals(store.mainIndex.getMaxTimestamp(), Double.POSITIVE_INFINITY); @@ -86,7 +86,7 @@ public void testGetMax() { @Test public void testGetElements() { - TimeStore timestampStore = new TimeStore(null, null, true); + TimeStore timestampStore = new TimeStore(null, true); TimestampIndexStore store = (TimestampIndexStore) timestampStore.nodeIndexStore; store.add(1.0); store.add(2.0); @@ -130,14 +130,14 @@ public void testGetElements() { @Test public void testHasNodesEdgesEmpty() { - TimeStore timestampStore = new TimeStore(null, null, true); + TimeStore timestampStore = new TimeStore(null, true); TimestampIndexStore store = (TimestampIndexStore) timestampStore.nodeIndexStore; Assert.assertFalse(store.mainIndex.hasElements()); } @Test public void testHasNodes() { - TimeStore timestampStore = new TimeStore(null, null, true); + TimeStore timestampStore = new TimeStore(null, true); TimestampIndexStore store = (TimestampIndexStore) timestampStore.nodeIndexStore; Assert.assertFalse(store.mainIndex.hasElements()); @@ -158,7 +158,7 @@ public void testHasNodes() { @Test public void testHasNodesClear() { - TimeStore timestampStore = new TimeStore(null, null, true); + TimeStore timestampStore = new TimeStore(null, true); TimestampIndexStore store = (TimestampIndexStore) timestampStore.nodeIndexStore; store.add(1.0); diff --git a/store/src/test/java/org/gephi/graph/impl/TimestampIndexStoreTest.java b/src/test/java/org/gephi/graph/impl/TimestampIndexStoreTest.java similarity index 87% rename from store/src/test/java/org/gephi/graph/impl/TimestampIndexStoreTest.java rename to src/test/java/org/gephi/graph/impl/TimestampIndexStoreTest.java index c93a119e..049af2e3 100644 --- a/store/src/test/java/org/gephi/graph/impl/TimestampIndexStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/TimestampIndexStoreTest.java @@ -451,6 +451,54 @@ public void testSetAttribute() { Assert.assertFalse(store.contains(2.0)); } + @Test + public void testSetAttributeTimestampCounts() { + GraphStore graphStore = new GraphStore(); + TimestampIndexStore store = (TimestampIndexStore) graphStore.timeStore.nodeIndexStore; + + Column col = graphStore.nodeTable.addColumn("col", TimestampStringMap.class); + NodeImpl nodeImpl = (NodeImpl) graphStore.factory.newNode("0"); + graphStore.addNode(nodeImpl); + + nodeImpl.setAttribute(col, "foo", 1.0); + nodeImpl.setAttribute(col, "bar", 2.0); + nodeImpl.setAttribute(col, "baz", 3.0); + // Overwriting an existing timestamp is not a new reference + nodeImpl.setAttribute(col, "qux", 1.0); + + Assert.assertEquals(store.size(), 3); + Assert.assertEquals(store.countMap[(Integer) store.timeSortedMap.get(1.0)], 1); + Assert.assertEquals(store.countMap[(Integer) store.timeSortedMap.get(2.0)], 1); + Assert.assertEquals(store.countMap[(Integer) store.timeSortedMap.get(3.0)], 1); + + // One reference each, so each removal frees its timestamp + nodeImpl.removeAttribute(col, 1.0); + Assert.assertFalse(store.contains(1.0)); + nodeImpl.removeAttribute(col); + Assert.assertEquals(store.size(), 0); + Assert.assertFalse(store.contains(2.0)); + Assert.assertFalse(store.contains(3.0)); + } + + @Test + public void testSetAttributeTimestampSharedWithTimeSet() { + GraphStore graphStore = new GraphStore(); + TimestampIndexStore store = (TimestampIndexStore) graphStore.timeStore.nodeIndexStore; + + Column col = graphStore.nodeTable.addColumn("col", TimestampStringMap.class); + NodeImpl nodeImpl = (NodeImpl) graphStore.factory.newNode("0"); + graphStore.addNode(nodeImpl); + + nodeImpl.addTimestamp(1.0); + nodeImpl.setAttribute(col, "foo", 1.0); + Assert.assertEquals(store.countMap[(Integer) store.timeSortedMap.get(1.0)], 2); + + nodeImpl.removeAttribute(col, 1.0); + Assert.assertTrue(store.contains(1.0)); + nodeImpl.removeTimestamp(1.0); + Assert.assertFalse(store.contains(1.0)); + } + @Test public void testRemoveAttributeTimestamp() { GraphStore graphStore = new GraphStore(); @@ -622,7 +670,7 @@ public void testClearElementWithView() { Graph graph = graphStore.viewStore.getGraph(view); view.fill(); TimeIndexImpl index = store.createViewIndex(graph); - n1.clearAttributes(); + n1.destroyAttributes(); Assert.assertFalse(index.hasElements()); } @@ -643,6 +691,33 @@ public void testClearViewWithView() { Assert.assertFalse(index.hasElements()); } + @Test + public void testGetIndexViewNotIndexed() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + TimestampIndexStore store = new TimestampIndexStore<>(Node.class, null, false); + GraphView view = graphStore.viewStore.createView(); + Graph graph = graphStore.viewStore.getGraph(view); + Assert.assertNull(store.getIndex(graph)); + } + + @Test + public void testClearInViewElementNotInView() { + GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); + NodeImpl n1 = graphStore.getNode("1"); + n1.addTimestamp(1.0); + + TimestampIndexStore store = (TimestampIndexStore) graphStore.timeStore.nodeIndexStore; + + GraphViewImpl view = graphStore.viewStore.createView(); + Graph graph = graphStore.viewStore.getGraph(view); + store.createViewIndex(graph); + + // n1 has timestamp 1.0 in timeSortedMap but was never added to the (empty) view + // index + // clearInView previously threw ArrayIndexOutOfBoundsException + store.clearInView(n1, view); + } + // UTILITY private Object[] getArrayFromIterable(Iterable iterable) { List list = new ArrayList<>(); diff --git a/store/src/test/java/org/gephi/graph/impl/TimestampsParserTest.java b/src/test/java/org/gephi/graph/impl/TimestampsParserTest.java similarity index 84% rename from store/src/test/java/org/gephi/graph/impl/TimestampsParserTest.java rename to src/test/java/org/gephi/graph/impl/TimestampsParserTest.java index 6fe70b4c..20bf5009 100644 --- a/store/src/test/java/org/gephi/graph/impl/TimestampsParserTest.java +++ b/src/test/java/org/gephi/graph/impl/TimestampsParserTest.java @@ -17,6 +17,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.format.DateTimeParseException; import java.util.Date; import java.util.TimeZone; import org.gephi.graph.api.types.TimestampBooleanMap; @@ -96,8 +97,6 @@ public void testParseTimestampSet() throws ParseException { // Dates: assertEquals(buildTimestampSet(parseDateIntoTimestamp("2015-01-01"), parseDateIntoTimestamp("2015-01-31")), TimestampsParser .parseTimestampSet("[2015-01-01, 2015-01-31]")); - assertEquals(buildTimestampSet(parseDateIntoTimestamp("2015-01-01"), parseDateIntoTimestamp("2015-01-31")), TimestampsParser - .parseTimestampSet("[2015-01, 2015-01-31]")); // Date times: assertEquals(buildTimestampSet(parseDateTimeIntoTimestamp("2015-01-01 21:12:05"), parseDateTimeIntoTimestamp("2015-01-02 00:00:00")), TimestampsParser @@ -140,7 +139,8 @@ public void testParseTimestampMapString() { expected.put(5.0, "Value 3"); expected.put(6.0, " Value 4 "); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(String.class, "[1, Value1]; [3, 'Value2']; [5, Value 3]; [6, \" Value 4 \"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(String.class, "[1, Value1]; [3, 'Value2']; [5, Value 3]; [6, \" Value 4 \"]")); } @Test @@ -151,8 +151,10 @@ public void testParseTimestampMapByte() { expected.put(6.0, (byte) 3); expected.put(7.0, (byte) 4); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(Byte.class, "[1, 1]; [3, 2]; [6, '3']; [7, \"4\"]")); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(byte.class, "[1, 1]; [3, 2]; [6, '3']; [7, \"4\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(Byte.class, "[1, 1]; [3, 2]; [6, '3']; [7, \"4\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(byte.class, "[1, 1]; [3, 2]; [6, '3']; [7, \"4\"]")); } @Test @@ -163,8 +165,10 @@ public void testParseTimestampMapShort() { expected.put(5.0, (short) 3); expected.put(6.0, (short) 4); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(Short.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(short.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(Short.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(short.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); } @Test @@ -175,8 +179,10 @@ public void testParseTimestampMapInteger() { expected.put(5.0, 3); expected.put(6.0, 4); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(Integer.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(int.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(Integer.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(int.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); } @Test @@ -187,8 +193,10 @@ public void testParseTimestampMapLong() { expected.put(5.0, 3l); expected.put(6.0, 4l); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(Long.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(long.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(Long.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(long.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); } @Test @@ -199,8 +207,10 @@ public void testParseTimestampMapFloat() { expected.put(5.0, 3f); expected.put(6.0, 4f); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(Float.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(float.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(Float.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(float.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); } @Test @@ -211,8 +221,10 @@ public void testParseTimestampMapDouble() { expected.put(5.0, 3d); expected.put(6.0, 4d); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(Double.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(double.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(Double.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(double.class, "[1, 1]; [3, 2]; [5, '3']; [6, \"4\"]")); } @Test @@ -223,8 +235,10 @@ public void testParseTimestampMapBoolean() { expected.put(5.0, false); expected.put(6.0, true); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(Boolean.class, "[1, true]; [3, false]; [5, '0']; [6, \"1\"]")); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(boolean.class, "[1, true]; [3, false]; [5, '0']; [6, \"1\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(Boolean.class, "[1, true]; [3, false]; [5, '0']; [6, \"1\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(boolean.class, "[1, true]; [3, false]; [5, '0']; [6, \"1\"]")); } @Test @@ -235,8 +249,10 @@ public void testParseTimestampMapChar() { expected.put(5.0, 'c'); expected.put(6.0, 'd'); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(Character.class, "[1, a]; [3, b]; [5, 'c']; [6, \"d\"]")); - assertEqualTimestampMaps(expected, TimestampsParser.parseTimestampMap(char.class, "[1, a]; [3, b]; [5, 'c']; [6, \"d\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(Character.class, "[1, a]; [3, b]; [5, 'c']; [6, \"d\"]")); + assertEqualTimestampMaps(expected, TimestampsParser + .parseTimestampMap(char.class, "[1, a]; [3, b]; [5, 'c']; [6, \"d\"]")); } @Test(expectedExceptions = IllegalArgumentException.class) diff --git a/store/src/test/java/org/gephi/graph/impl/UndirectedDecoratorTest.java b/src/test/java/org/gephi/graph/impl/UndirectedDecoratorTest.java similarity index 97% rename from store/src/test/java/org/gephi/graph/impl/UndirectedDecoratorTest.java rename to src/test/java/org/gephi/graph/impl/UndirectedDecoratorTest.java index 0ca95d96..41d47d29 100644 --- a/store/src/test/java/org/gephi/graph/impl/UndirectedDecoratorTest.java +++ b/src/test/java/org/gephi/graph/impl/UndirectedDecoratorTest.java @@ -15,12 +15,10 @@ */ package org.gephi.graph.impl; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; -import java.util.Iterator; -import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.gephi.graph.api.Edge; import org.gephi.graph.api.EdgeIterable; import org.gephi.graph.api.Node; @@ -205,7 +203,11 @@ public void testGetEdgesMixed() { // UTILITY private void testEdgeIterable(EdgeIterable iterable, Edge[] edges) { - Set edgeSet = new HashSet<>(iterable.toCollection()); + testEdgeIterable(new HashSet<>(iterable.toCollection()), edges); + testEdgeIterable(iterable.stream().collect(Collectors.toSet()), edges); + } + + private void testEdgeIterable(Set edgeSet, Edge[] edges) { for (Edge n : edges) { Assert.assertTrue(edgeSet.remove(n)); } diff --git a/src/test/resources/serialization/0.4/graph-basic.graphstore b/src/test/resources/serialization/0.4/graph-basic.graphstore new file mode 100644 index 00000000..052bdabb Binary files /dev/null and b/src/test/resources/serialization/0.4/graph-basic.graphstore differ diff --git a/src/test/resources/serialization/0.4/graph-parallel.graphstore b/src/test/resources/serialization/0.4/graph-parallel.graphstore new file mode 100644 index 00000000..6f39a5fe Binary files /dev/null and b/src/test/resources/serialization/0.4/graph-parallel.graphstore differ diff --git a/src/test/resources/serialization/0.5/graph-basic.graphstore b/src/test/resources/serialization/0.5/graph-basic.graphstore new file mode 100644 index 00000000..7359f3a6 Binary files /dev/null and b/src/test/resources/serialization/0.5/graph-basic.graphstore differ diff --git a/src/test/resources/serialization/0.5/graph-parallel.graphstore b/src/test/resources/serialization/0.5/graph-parallel.graphstore new file mode 100644 index 00000000..28a6e22f Binary files /dev/null and b/src/test/resources/serialization/0.5/graph-parallel.graphstore differ diff --git a/src/test/resources/serialization/0.6/graph-basic.graphstore b/src/test/resources/serialization/0.6/graph-basic.graphstore new file mode 100644 index 00000000..0994b7f0 Binary files /dev/null and b/src/test/resources/serialization/0.6/graph-basic.graphstore differ diff --git a/src/test/resources/serialization/0.6/graph-parallel.graphstore b/src/test/resources/serialization/0.6/graph-parallel.graphstore new file mode 100644 index 00000000..f9092ad0 Binary files /dev/null and b/src/test/resources/serialization/0.6/graph-parallel.graphstore differ diff --git a/src/test/resources/serialization/0.7/graph-basic.graphstore b/src/test/resources/serialization/0.7/graph-basic.graphstore new file mode 100644 index 00000000..7359f3a6 Binary files /dev/null and b/src/test/resources/serialization/0.7/graph-basic.graphstore differ diff --git a/src/test/resources/serialization/0.7/graph-parallel.graphstore b/src/test/resources/serialization/0.7/graph-parallel.graphstore new file mode 100644 index 00000000..28a6e22f Binary files /dev/null and b/src/test/resources/serialization/0.7/graph-parallel.graphstore differ diff --git a/src/test/resources/serialization/0.8/graph-basic.graphstore b/src/test/resources/serialization/0.8/graph-basic.graphstore new file mode 100644 index 00000000..7359f3a6 Binary files /dev/null and b/src/test/resources/serialization/0.8/graph-basic.graphstore differ diff --git a/src/test/resources/serialization/0.8/graph-parallel.graphstore b/src/test/resources/serialization/0.8/graph-parallel.graphstore new file mode 100644 index 00000000..28a6e22f Binary files /dev/null and b/src/test/resources/serialization/0.8/graph-parallel.graphstore differ diff --git a/src/test/resources/serialization/0.8/graph-types-interval.graphstore b/src/test/resources/serialization/0.8/graph-types-interval.graphstore new file mode 100644 index 00000000..eeea1d47 Binary files /dev/null and b/src/test/resources/serialization/0.8/graph-types-interval.graphstore differ diff --git a/src/test/resources/serialization/0.8/graph-types-timestamp.graphstore b/src/test/resources/serialization/0.8/graph-types-timestamp.graphstore new file mode 100644 index 00000000..346853f5 Binary files /dev/null and b/src/test/resources/serialization/0.8/graph-types-timestamp.graphstore differ diff --git a/src/test/resources/serialization/0.8/graph-views.graphstore b/src/test/resources/serialization/0.8/graph-views.graphstore new file mode 100644 index 00000000..8fa2ceac Binary files /dev/null and b/src/test/resources/serialization/0.8/graph-views.graphstore differ diff --git a/src/test/resources/serialization/README.md b/src/test/resources/serialization/README.md new file mode 100644 index 00000000..2af87989 --- /dev/null +++ b/src/test/resources/serialization/README.md @@ -0,0 +1,51 @@ +# Serialization golden fixtures + +Golden files used by `org.gephi.graph.impl.SerializationCompatibilityTest` to guard the on-disk serialization format. + +## Layout + +One directory per minor version: + +``` +serialization/ + 0.4/ graph-basic, graph-parallel + 0.5/ graph-basic, graph-parallel + 0.6/ graph-basic, graph-parallel + 0.7/ graph-basic, graph-parallel + 0.8/ graph-basic, graph-parallel, graph-types-timestamp, graph-types-interval, graph-views +``` + +Patch versions are not tracked separately; the format is stable within a minor. 0.6.13 and 0.6.14 are byte-identical, +so 0.6 holds one copy. + +Each file is a raw `GraphModel.Serialization.write(...)` dump over a `DataOutputStream`, with no compression. Read them +with `GraphModel.Serialization.read(new DataInputStream(...))`. + +## Contract + +A minor bump may change the byte format. Older formats must remain readable. + +1. Every fixture from 0.4 up deserializes and holds the expected content. +2. For the current minor, serializing the model built by `SerializationFixtureGenerator` reproduces the committed + bytes. Older minors are exempt, since graphstore no longer writes those formats. +3. The same content serializes to the same bytes regardless of build order. + +## Regenerating + +Regeneration applies to the current version only. The legacy directories are historical artifacts and are never +rewritten. + +``` +mvn -q test-compile +mvn -q dependency:build-classpath -Dmdep.outputFile=target/test-cp.txt +java -cp "target/classes:target/test-classes:$(cat target/test-cp.txt)" \ + org.gephi.graph.impl.SerializationFixtureGenerator +``` + +The fixture root defaults to `src/test/resources/serialization` and can be passed as the single argument. + +A failing byte-pin means either a bug or an intended format change. An intended change is committed together with the +regenerated fixtures, plus a `Serialization.VERSION` bump if it breaks read compatibility. + +When the project moves to a new minor: create its directory, generate its fixtures, bump +`SerializationFixtureGenerator.CURRENT_MINOR`, and add the minor to `SerializationCompatibilityTest.ALL_MINORS`. \ No newline at end of file diff --git a/store-benchmark/pom.xml b/store-benchmark/pom.xml deleted file mode 100644 index eb1a2e7e..00000000 --- a/store-benchmark/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - 4.0.0 - - org.gephi - graphstore-benchmark - 0.6.1-SNAPSHOT - jar - - graphstore-benchmark - http://maven.apache.org - - - UTF-8 - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - - org.apache.maven.plugins - maven-surefire-plugin - 2.17 - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-surefire-plugin - - -Xmx2g - - - - - - - - org.testng - testng - 6.8.5 - test - - - ${project.groupId} - graphstore - 0.6.1-SNAPSHOT - - - diff --git a/store-benchmark/src/main/java/org/gephi/graph/benchmark/DataStructureBenchmark.java b/store-benchmark/src/main/java/org/gephi/graph/benchmark/DataStructureBenchmark.java deleted file mode 100644 index cbb8904f..00000000 --- a/store-benchmark/src/main/java/org/gephi/graph/benchmark/DataStructureBenchmark.java +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.benchmark; - -import cern.colt.bitvector.BitVector; -import cern.colt.map.OpenIntObjectHashMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap; -import it.unimi.dsi.fastutil.ints.IntAVLTreeSet; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntIterator; -import it.unimi.dsi.fastutil.ints.IntList; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectBigArrayBigList; -import it.unimi.dsi.fastutil.objects.ObjectBigList; -import it.unimi.dsi.fastutil.objects.ObjectList; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import org.gephi.graph.api.types.TimestampSet; - -import java.util.ArrayList; -import java.util.BitSet; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Random; - -/** - * - * @author mbastian - */ -public class DataStructureBenchmark { - - private static int NODES = 500000; - private static int LOW_NODES = 50000; - private Object object; - - /** - * Insertion and memory usage for Int2ObjectOpenHashMap - */ - public Runnable openHashMapMemory() { - return () -> { - int nodes = NODES; - Int2ObjectMap map = new Int2ObjectOpenHashMap(nodes); - for (int i = 0; i < nodes; i++) { - map.put(i, new Object()); - } - object = map; - }; - } - - /** - * Insertion and memory usage for Int2ObjectOpenHashMap without original - * capcity - */ - public Runnable dynamicOpenHashMapMemory() { - return () -> { - int nodes = NODES; - Int2ObjectMap map = new Int2ObjectOpenHashMap(); - for (int i = 0; i < nodes; i++) { - map.put(i, new Object()); - } - object = map; - }; - } - - /** - * Insertion and memory usage for Int2ObjectRBTreeMap - */ - public Runnable rbHashMapMemory() { - return () -> { - int nodes = NODES; - Int2ObjectMap map = new Int2ObjectRBTreeMap(); - for (int i = 0; i < nodes; i++) { - map.put(i, new Object()); - } - object = map; - }; - } - - /** - * Insertion and memory usage for OpenIntObjectHashMap - */ - public Runnable coltOpenHashMapMemory() { - return () -> { - int nodes = NODES; - OpenIntObjectHashMap map = new OpenIntObjectHashMap(nodes); - for (int i = 0; i < nodes; i++) { - map.put(i, new Object()); - } - object = map; - }; - } - - /** - * Insertion and memory usage for basic array - */ - public Runnable arrayMemory() { - return () -> { - int nodes = NODES; - Object[] array = new Object[nodes]; - for (int i = 0; i < nodes; i++) { - array[i] = new Object(); - } - object = array; - }; - } - - /** - * Insertion and memory usage for object list - */ - public Runnable objectListMemory() { - return () -> { - int nodes = NODES; - final ObjectList list = new ObjectArrayList(nodes); - for (int i = 0; i < nodes; i++) { - list.add(1); - } - object = list; - }; - } - - public Runnable dynamicObjectListMemory() { - return () -> { - final ObjectList list = new ObjectArrayList(); - int nodes = NODES; - for (int i = 0; i < nodes; i++) { - list.add(1); - } - object = list; - }; - } - - /** - * Insertion and memory usage for object list - */ - public Runnable objectBigListMemory() { - return () -> { - int nodes = NODES; - final ObjectBigList list = new ObjectBigArrayBigList(nodes); - for (int i = 0; i < nodes; i++) { - list.add(1); - } - object = list; - }; - } - - public Runnable openHashMapIteration() { - int nodes = NODES; - final Int2ObjectMap map = new Int2ObjectOpenHashMap(nodes); - for (int i = 0; i < nodes; i++) { - map.put(i, new Integer(1)); - } - return () -> { - int sum = 0; - for (Object i : map.values()) { - sum += (Integer) i; - } - object = sum; - }; - } - - public Runnable rbHashMapIteration() { - int nodes = NODES; - final Int2ObjectMap map = new Int2ObjectRBTreeMap(); - for (int i = 0; i < nodes; i++) { - map.put(i, new Integer(1)); - } - return () -> { - int sum = 0; - for (Object i : map.values()) { - sum += (Integer) i; - } - object = sum; - }; - } - - public Runnable arrayIteration() { - //Create array - int nodes = NODES; - final Object[] array = new Object[nodes]; - for (int i = 0; i < nodes; i++) { - array[i] = 1; - } - return () -> { - int sum = 0; - for (Object i : array) { - sum += (Integer) i; - } - object = sum; - }; - } - - public Runnable linkedListIteration() { - //Create array - int nodes = NODES; - final LinkedList list = new LinkedList(); - for (int i = 0; i < nodes; i++) { - list.add(1); - } - return () -> { - int sum = 0; - for (Object i : list) { - sum += (Integer) i; - } - object = sum; - }; - } - - public Runnable objectListIteration() { - int nodes = NODES; - final ObjectList list = new ObjectArrayList(nodes); - for (int i = 0; i < nodes; i++) { - list.add(1); - } - return () -> { - int sum = 0; - for (Object i : list) { - sum += (Integer) i; - } - object = sum; - }; - } - - public Runnable objectBigListIteration() { - int nodes = NODES; - final ObjectBigList list = new ObjectBigArrayBigList(nodes); - for (int i = 0; i < nodes; i++) { - list.add(1); - } - return () -> { - int sum = 0; - for (Object i : list) { - sum += (Integer) i; - } - object = sum; - }; - } - - public Runnable objectLinkedHashMapIteration() { - int nodes = NODES; - final Int2ObjectLinkedOpenHashMap list = new Int2ObjectLinkedOpenHashMap(nodes); - for (int i = 0; i < nodes; i++) { - list.put(new Integer(i), new Integer(1)); - } - return () -> { - int sum = 0; - for (Object i : list.values()) { - sum += (Integer) i; - } - object = sum; - }; - } - - public Runnable objectHashObjectSet() { - int nodes = NODES; - final ObjectOpenHashSet list = new ObjectOpenHashSet(nodes); - for (int i = 0; i < nodes; i++) { - list.add(i); - } - return () -> { - int sum = 0; - for (Object i : list) { - sum += (Integer) i; - } - object = sum; - }; - } - - public Runnable objectAVLObjectSet() { - int nodes = NODES; - final ObjectAVLTreeSet list = new ObjectAVLTreeSet(); - for (int i = 0; i < nodes; i++) { - list.add(i); - } - return () -> { - int sum = 0; - for (Object i : list) { - sum += (Integer) i; - } - object = sum; - }; - } - - public Runnable openHashMapResetAll() { - int nodes = LOW_NODES; - final Int2IntOpenHashMap map = new Int2IntOpenHashMap(nodes); - for (int i = 0; i < nodes; i++) { - map.put(i, i); - } - return () -> { - int nodes1 = LOW_NODES; - for (int i = 0; i < nodes1; i++) { - map.put(i, i); - } - }; - } - - public Runnable arrayResetAll() { - int nodes = LOW_NODES; - final int[] array = new int[nodes]; - for (int i = 0; i < nodes; i++) { - array[i] = i; - } - return () -> { - int nodes1 = LOW_NODES; - for (int i = 0; i < nodes1; i++) { - array[i] = i; - } - }; - } - - public Runnable objectListResetAll() { - int nodes = LOW_NODES; - final IntList list = new IntArrayList(nodes); - for (int i = 0; i < nodes; i++) { - list.add(i); - } - return () -> { - int nodes1 = LOW_NODES; - for (int i = 0; i < nodes1; i++) { - list.set(i, i); - } - }; - } - - public Runnable fastutilObject2IntIterate() { - int nodes = NODES; - final Object2IntMap map = new Object2IntOpenHashMap<>(); - for (int i = 0; i < nodes; i++) { - map.put("" + i, i); - } - final Random rand = new Random(456); - return () -> { - int matches = 0; - for (int i = 0; i < 50000; i++) { - int id = rand.nextInt(NODES - 1); - if (map.containsKey("" + id)) { - matches += id; - } - } - object = matches; - }; - } - - public Runnable javaObject2IntIterate() { - int nodes = NODES; - final Map map = new HashMap<>(); - for (int i = 0; i < nodes; i++) { - map.put("" + i, i); - } - final Random rand = new Random(456); - return () -> { - int matches = 0; - for (int i = 0; i < 50000; i++) { - int id = rand.nextInt(NODES - 1); - if (map.containsKey("" + id)) { - matches += id; - } - } - object = matches; - }; - } - - public Runnable javaArrayIterate() { - final boolean[] bitset = new boolean[NODES]; - Random rand = new Random(23); - for (int i = 0; i < NODES; i++) { - bitset[i] = rand.nextBoolean(); - } - return () -> { - int cardinality = 0; - for (int i = 0; i < NODES; i++) { - boolean b = bitset[i]; - if (b) { - cardinality++; - } - } - object = cardinality; - }; - } - - public Runnable javaBitVectorIterate() { - final BitSet bitset = new BitSet(NODES); - Random rand = new Random(23); - for (int i = 0; i < NODES; i++) { - bitset.set(i, rand.nextBoolean()); - } - return () -> { - int cardinality = 0; - for (int i = 0; i < NODES; i++) { - boolean b = bitset.get(i); - if (b) { - cardinality++; - } - } - object = cardinality; - }; - } - - public Runnable coltBitVector() { - final BitVector bitset = new BitVector(NODES); - Random rand = new Random(23); - for (int i = 0; i < NODES; i++) { - if (rand.nextBoolean()) { - bitset.set(i); - } - } - return () -> { - int cardinality = 0; - for (int i = 0; i < NODES; i++) { - boolean b = bitset.get(i); - if (b) { - cardinality++; - } - } - object = cardinality; - }; - } - - public Runnable coltBitVectorIterateAndMapLookup() { - final BitVector bitset = new BitVector(NODES); - final Int2IntOpenHashMap map = new Int2IntOpenHashMap(); - Random rand = new Random(23); - for (int i = 0; i < NODES; i++) { - if (rand.nextBoolean()) { - bitset.set(i); - map.put(i, i); - } - } - map.trim(); - return () -> { - int cardinality = 0; - for (int i = 0; i < NODES; i++) { - boolean b = bitset.get(i); - if (b) { - long rank = map.get(i); - cardinality += rank; - } - } - object = cardinality; - }; - } - - public Runnable javaBitVectorMemory() { - - return () -> { - BitSet bitset = new BitSet(10000000); - Random rand = new Random(23); - for (int i = 0; i < 10000000; i++) { - bitset.set(i, rand.nextBoolean()); - } - object = bitset; - }; - } - - public Runnable coltBitVectorMemory() { - - return () -> { - BitVector bitset = new BitVector(10000000); - Random rand = new Random(23); - for (int i = 0; i < 10000000; i++) { - if (rand.nextBoolean()) { - bitset.set(i); - } - } - object = bitset; - }; - } - - public Runnable sparseArrayIterate(float emptyRatio) { - final BitVector bitset = new BitVector(NODES); - final int[] values = new int[NODES]; - Random rand2 = new Random(456); - Random rand = new Random(23); - for (int i = 0; i < NODES; i++) { - if (rand.nextDouble() < emptyRatio) { - bitset.set(i); - } - values[i] = rand2.nextInt(NODES); - } - return () -> { - int cardinality = 0; - for (int i = 0; i < NODES; i++) { - boolean b = bitset.get(i); - if (b) { - int val = values[i]; - cardinality += val; - } - } - object = cardinality; - }; - } - - public Runnable intHashSetIterate(float emptyRatio) { - int nodes = (int) (NODES * (1 - emptyRatio)); - Random rand2 = new Random(456); - final IntSet values = new IntOpenHashSet(nodes); - for (int i = 0; i < nodes; i++) { - values.add(rand2.nextInt(NODES)); - } - return () -> { - int cardinality = 0; - IntIterator itr = values.iterator(); - while (itr.hasNext()) { - int i = itr.nextInt(); - cardinality += i; - } - object = cardinality; - }; - } - - public Runnable intAVLSetIterate(float emptyRatio) { - int nodes = (int) (NODES * (1 - emptyRatio)); - Random rand2 = new Random(456); - final IntSet values = new IntAVLTreeSet(); - for (int i = 0; i < nodes; i++) { - values.add(rand2.nextInt(NODES)); - } - return () -> { - int cardinality = 0; - IntIterator itr = values.iterator(); - while (itr.hasNext()) { - int i = itr.nextInt(); - cardinality += i; - } - object = cardinality; - }; - } - - public Runnable arraySetMemory() { - final int size = 10000; - final int timestamps = 100; - - return () -> { - List trees = new ArrayList<>(); - Random rand = new Random(1234); - for (int i = 0; i < size; i++) { - TimestampSet set = new TimestampSet(timestamps); - for (int j = 0; j < timestamps; j++) { - final double val = rand.nextInt(timestamps); - set.add(val); - trees.add(set); - } - } - object = trees; - }; - } -} diff --git a/store-benchmark/src/main/java/org/gephi/graph/benchmark/EdgeStoreBenchmark.java b/store-benchmark/src/main/java/org/gephi/graph/benchmark/EdgeStoreBenchmark.java deleted file mode 100644 index e36f450b..00000000 --- a/store-benchmark/src/main/java/org/gephi/graph/benchmark/EdgeStoreBenchmark.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.gephi.graph.benchmark; - -import java.util.Iterator; -import java.util.List; - -import org.gephi.graph.api.Configuration; -import org.gephi.graph.api.Edge; -import org.gephi.graph.api.Node; -import org.gephi.graph.impl.EdgeStore; - -/** - * - * @author mbastian, niteshbhargv - */ -public class EdgeStoreBenchmark { - - private Object object; - - public Runnable pushEdgeStore(int nodes, double prob) { - final Configuration config = new Configuration(); - config.setEdgeIdType(Integer.class); - config.setNodeIdType(Integer.class); - final RandomGraph graph = new RandomGraph(nodes, prob, config).generate(); - final EdgeStore edgeStore = graph.getStore().getEdgeStore(); - final List nodeList = graph.getNodes(); - final List edgeList = graph.getEdges(); - graph.getStore().addAllNodes(nodeList); - - Runnable runnable = () -> { - edgeStore.clear(); - for(Edge edge : edgeList) { - edgeStore.add(edge); - } - }; - return runnable; - } - - public Runnable iterateEdgeStore(int nodes, double prob) { - final Configuration config = new Configuration(); - config.setEdgeIdType(Integer.class); - config.setNodeIdType(Integer.class); - final RandomGraph graph = new RandomGraph(nodes, prob, config).generate().commit(); - final EdgeStore edgeStore = graph.getStore().getEdgeStore(); - - Runnable runnable = () -> { - Iterator itr = edgeStore.iterator(); - for (; itr.hasNext();) { - object = itr.next(); - } - }; - return runnable; - } - - public Runnable iterateEdgeStoreNeighborsOut(int nodes, double prob) { - final Configuration config = new Configuration(); - config.setEdgeIdType(Integer.class); - config.setNodeIdType(Integer.class); - final RandomGraph graph = new RandomGraph(nodes, prob, config).generate().commit(); - final EdgeStore edgeStore = graph.getStore().getEdgeStore(); - final List nodeList = graph.getNodes(); - - Runnable runnable = () -> { - for (Node node : nodeList) { - Iterator itr = edgeStore.edgeOutIterator(node); - for (; itr.hasNext();) { - object = itr.next(); - } - } - }; - return runnable; - } - - public Runnable iterateEdgeStoreNeighborsInOut(int nodes, double prob) { - final Configuration config = new Configuration(); - config.setEdgeIdType(Integer.class); - config.setNodeIdType(Integer.class); - final RandomGraph graph = new RandomGraph(nodes, prob, config).generate().commit(); - final EdgeStore edgeStore = graph.getStore().getEdgeStore(); - final List nodeList = graph.getNodes(); - - Runnable runnable = () -> { - for (Node node : nodeList) { - Iterator itr = edgeStore.edgeIterator(node); - for (; itr.hasNext();) { - object = itr.next(); - } - } - }; - return runnable; - } - - public Runnable resetEdgeStore(int nodes, double prob) { - final Configuration config = new Configuration(); - config.setEdgeIdType(Integer.class); - config.setNodeIdType(Integer.class); - final RandomGraph graph = new RandomGraph(nodes, prob, config).generate().commit(); - final EdgeStore edgeStore = graph.getStore().getEdgeStore(); - final List edgeList = graph.getEdges(); - - Runnable runnable = () -> { - for (Edge e : edgeList) { - edgeStore.remove(e); - } - for (Edge e : edgeList) { - edgeStore.add(e); - } - }; - return runnable; - } -} diff --git a/store-benchmark/src/main/java/org/gephi/graph/benchmark/Generator.java b/store-benchmark/src/main/java/org/gephi/graph/benchmark/Generator.java deleted file mode 100644 index ec8ee1da..00000000 --- a/store-benchmark/src/main/java/org/gephi/graph/benchmark/Generator.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.gephi.graph.benchmark; - -import java.util.ArrayList; -import java.util.List; - -import org.gephi.graph.api.Configuration; -import org.gephi.graph.api.Edge; -import org.gephi.graph.api.GraphFactory; -import org.gephi.graph.api.Node; -import org.gephi.graph.impl.GraphModelImpl; -import org.gephi.graph.impl.GraphStore; - -public abstract class Generator { - - protected final GraphFactory factory; - protected final GraphStore graphStore; - protected List nodes; - protected List edges; - - public Generator() { - this(new Configuration()); - } - - public Generator(final Configuration config) { - GraphModelImpl model = new GraphModelImpl(config); - factory = model.factory(); - graphStore = model.getStore(); - nodes = new ArrayList<>(); - edges = new ArrayList<>(); - } - - public GraphStore getStore() { - return graphStore; - } - - public List getNodes() { - return nodes; - } - - public List getEdges() { - return edges; - } - - public void clean() { - nodes = null; - edges = null; - } - - public abstract Generator generate(); - - public abstract Generator commit(); - - protected void commitInner() { - for (Node node : nodes) { - graphStore.addNode(node); - } - for (Edge edge : edges) { - graphStore.addEdge(edge); - } - } -} diff --git a/store-benchmark/src/main/java/org/gephi/graph/benchmark/KleinbergGraph.java b/store-benchmark/src/main/java/org/gephi/graph/benchmark/KleinbergGraph.java deleted file mode 100644 index 46019a24..00000000 --- a/store-benchmark/src/main/java/org/gephi/graph/benchmark/KleinbergGraph.java +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.benchmark; - -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import org.gephi.graph.api.Edge; -import org.gephi.graph.api.Node; - -import java.util.Random; - -/** - * Generates a directed connected graph. - * - * http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.117.7097&rep=rep1&type=pdf - * http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.83.381&rep=rep1&type=pdf - * - * n >= 2 p >= 1 p <= 2n - 2 q >= 0 q <= n^2 - p * (p + 3) / 2 - 1 for p < n q - * <= (2n - p - 3) * (2n - p) / 2 + 1 for p >= n r >= 0 - * - * Ω(n^4 * q) - * - * @author Nitesh Bhargava - */ -public class KleinbergGraph extends Generator { - - private int n = 5; - private int p = 2; - private int q = 2; - private int r = 0; - private boolean torusBased; - - /** - * User defined Kleinberg Graph no*no = number of nodes local = local - * contacts Long = long range contacts - */ - KleinbergGraph(int no, int local, int longRange) { - super(); - n = no; - p = local; - q = longRange; - r = 0; - torusBased = false; - } - - @Override - public KleinbergGraph generate() { - Random random = new Random(); - - // Creating lattice n x n - for (int i = 0; i < n; ++i) { - for (int j = 0; j < n; ++j) { - Node node = factory.newNode(); - nodes.add(node); - } - } - LongOpenHashSet edgeSet = new LongOpenHashSet(); - - // Creating edges from each node to p local contacts - for (int i = 0; i < n; ++i) { - for (int j = 0; j < n; ++j) { - for (int k = i - p; k <= i + p; ++k) { - for (int l = j - p; l <= j + p; ++l) { - if ((isTorusBased() || !isTorusBased() && k >= 0 && k < n && l >= 0 && l < n) - && d(i, j, k, l) <= p && nodes.get(i * n + j) != nodes.get(((k + n) % n) * n + ((l + n) % n))) { - Edge edge = factory.newEdge(nodes.get(i * n + j), nodes.get(((k + n) % n) * n + ((l + n) % n)), 0, true); - Object id = edge.getId(); - if (id instanceof Number) { - edges.add(edge); - edgeSet.add(((Number) id).longValue()); - } - } - } - } - } - } - - // Creating edges from each node to q long-range contacts - for (int i = 0; i < n; ++i) { - for (int j = 0; j < n; ++j) { - double sum = 0.0; - for (int k = 0; k < n; ++k) { - for (int l = 0; l < n; ++l) { - if (!isTorusBased() && d(i, j, k, l) > p) { - sum += Math.pow(d(i, j, k, l), -r); - } else if (isTorusBased() && dtb(i, j, k, l) > p) { - sum += Math.pow(dtb(i, j, k, l), -r); - } - - } - } - for (int m = 0; m < q; ++m) { - double b = random.nextDouble(); - boolean e = false; - while (!e) { - double pki = 0.0; - for (int k = 0; k < n && !e; ++k) { - for (int l = 0; l < n && !e; ++l) { - if (!isTorusBased() && d(i, j, k, l) > p || isTorusBased() && dtb(i, j, k, l) > p) { - pki += Math.pow(!isTorusBased() ? d(i, j, k, l) : dtb(i, j, k, l), -r) / sum; - Edge edge = factory.newEdge(nodes.get(i * n + j), nodes.get(k * n + l), 0, true); - Object id = edge.getId(); - if (id instanceof Number) { - if (b <= pki && !edgeSet.contains(((Number) id).longValue())) { - edges.add(edge); - edgeSet.add(((Number) id).longValue()); - e = true; - } - } - } - } - } - b = random.nextDouble(); - } - - } - } - } - return this; - } - - @Override - public KleinbergGraph commit() { - commitInner(); - return this; - } - - private int d(int i, int j, int k, int l) { - return Math.abs(k - i) + Math.abs(l - j); - } - - private int dtb(int i, int j, int k, int l) { - return Math.min(Math.abs(k - i), n - Math.abs(k - i)) + Math.min(Math.abs(l - j), n - Math.abs(l - j)); - } - - public int getn() { - return n; - } - - public int getp() { - return p; - } - - public int getq() { - return q; - } - - public int getr() { - return r; - } - - public boolean isTorusBased() { - return torusBased; - } - - public void setn(int n) { - this.n = n; - } - - public void setp(int p) { - this.p = p; - } - - public void setq(int q) { - this.q = q; - } - - public void setr(int r) { - this.r = r; - } - - public void setTorusBased(boolean torusBased) { - this.torusBased = torusBased; - } -} diff --git a/store-benchmark/src/main/java/org/gephi/graph/benchmark/LockingBenchmark.java b/store-benchmark/src/main/java/org/gephi/graph/benchmark/LockingBenchmark.java deleted file mode 100644 index 1e40ba25..00000000 --- a/store-benchmark/src/main/java/org/gephi/graph/benchmark/LockingBenchmark.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.benchmark; - -import java.util.Random; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class LockingBenchmark { - - private final DataStruture struture = new DataStruture(); - private double number; - private final int READS = 500; - private final int WRITES = 100; - private final int READER_THREADS = 4; - private final int WRITER_THREADS = 4; - - public Runnable readWithoutLock() { - return () -> { - for (int i = 0; i < READS; i++) { - struture.read(); - } - }; - } - - public Runnable readWithLock() { - final ReadWriteLock lock = new ReentrantReadWriteLock(true); - return () -> { - for (int i = 0; i < READS; i++) { - lock.readLock().lock(); - struture.read(); - lock.readLock().unlock(); - } - }; - } - - public Runnable fairReadOnly() { - final ReadWriteLock lock = new ReentrantReadWriteLock(true); - return () -> { - Runnable r = () -> { - for (int i = 0; i < READS; i++) { - lock.readLock().lock(); - struture.read(); - lock.readLock().unlock(); - } - }; - Thread[] threads = new Thread[READER_THREADS]; - for (int i = 0; i < READER_THREADS; i++) { - Thread thread = new Thread(r); - thread.start(); - threads[i] = thread; - } - for (Thread t : threads) { - try { - t.join(); - } catch (InterruptedException ex) { - Logger.getLogger(LockingBenchmark.class.getName()).log(Level.SEVERE, null, ex); - } - } - }; - } - - public Runnable unfairReadOnly() { - final ReadWriteLock lock = new ReentrantReadWriteLock(false); - return () -> { - Runnable r = () -> { - for (int i = 0; i < READS; i++) { - lock.readLock().lock(); - struture.read(); - lock.readLock().unlock(); - } - }; - Thread[] threads = new Thread[READER_THREADS]; - for (int i = 0; i < READER_THREADS; i++) { - Thread thread = new Thread(r); - thread.start(); - threads[i] = thread; - } - for (Thread t : threads) { - try { - t.join(); - } catch (InterruptedException ex) { - Logger.getLogger(LockingBenchmark.class.getName()).log(Level.SEVERE, null, ex); - } - } - }; - } - - public Runnable fairWriteOnly() { - final ReadWriteLock lock = new ReentrantReadWriteLock(true); - return () -> { - Runnable r = () -> { - for (int i = 0; i < WRITES; i++) { - lock.writeLock().lock(); - struture.write(); - lock.writeLock().unlock(); - } - }; - Thread[] threads = new Thread[READER_THREADS]; - for (int i = 0; i < READER_THREADS; i++) { - Thread thread = new Thread(r); - thread.start(); - threads[i] = thread; - } - for (Thread t : threads) { - try { - t.join(); - } catch (InterruptedException ex) { - Logger.getLogger(LockingBenchmark.class.getName()).log(Level.SEVERE, null, ex); - } - } - }; - } - - public Runnable unfairWriteOnly() { - final ReadWriteLock lock = new ReentrantReadWriteLock(false); - return () -> { - Runnable r = () -> { - for (int i = 0; i < WRITES; i++) { - lock.writeLock().lock(); - struture.write(); - lock.writeLock().unlock(); - } - }; - Thread[] threads = new Thread[READER_THREADS]; - for (int i = 0; i < READER_THREADS; i++) { - Thread thread = new Thread(r); - thread.start(); - threads[i] = thread; - } - for (Thread t : threads) { - try { - t.join(); - } catch (InterruptedException ex) { - Logger.getLogger(LockingBenchmark.class.getName()).log(Level.SEVERE, null, ex); - } - } - }; - } - - public Runnable fairReadWrites() { - final ReadWriteLock lock = new ReentrantReadWriteLock(true); - return () -> { - Runnable reader = () -> { - for (int i = 0; i < READS; i++) { - lock.readLock().lock(); - struture.read(); - lock.readLock().unlock(); - } - }; - Runnable writer = () -> { - for (int i = 0; i < WRITES; i++) { - lock.writeLock().lock(); - struture.write(); - lock.writeLock().unlock(); - } - }; - Thread[] threads = new Thread[READER_THREADS + WRITER_THREADS]; - for (int i = 0; i < READER_THREADS; i++) { - Thread thread = new Thread(reader); - threads[i] = thread; - } - for (int i = 0; i < WRITER_THREADS; i++) { - Thread thread = new Thread(writer); - threads[READER_THREADS + i] = thread; - } - for (Thread thread : threads) { - thread.start(); - } - for (Thread t : threads) { - try { - t.join(); - } catch (InterruptedException ex) { - Logger.getLogger(LockingBenchmark.class.getName()).log(Level.SEVERE, null, ex); - } - } - }; - } - - public Runnable unfairReadWrites() { - final ReadWriteLock lock = new ReentrantReadWriteLock(false); - return () -> { - Runnable reader = () -> { - for (int i = 0; i < READS; i++) { - lock.readLock().lock(); - struture.read(); - lock.readLock().unlock(); - } - }; - Runnable writer = () -> { - for (int i = 0; i < WRITES; i++) { - lock.writeLock().lock(); - struture.write(); - lock.writeLock().unlock(); - } - }; - Thread[] threads = new Thread[READER_THREADS + WRITER_THREADS]; - for (int i = 0; i < READER_THREADS; i++) { - Thread thread = new Thread(reader); - threads[i] = thread; - } - for (int i = 0; i < WRITER_THREADS; i++) { - Thread thread = new Thread(writer); - threads[READER_THREADS + i] = thread; - } - for (Thread thread : threads) { - thread.start(); - } - for (Thread t : threads) { - try { - t.join(); - } catch (InterruptedException ex) { - Logger.getLogger(LockingBenchmark.class.getName()).log(Level.SEVERE, null, ex); - } - } - }; - } - - private class DataStruture { - - private final int[] values = new int[10000]; - private final int readLoops = 10; - private final int writeLoops = 5; - - public DataStruture() { - Random rand = new Random(454); - for (int i = 0; i < values.length; i++) { - values[i] = rand.nextInt(values.length); - } - } - - public void write() { - Random rand = new Random(45445); - for (int i = 0; i < writeLoops; i++) { - for (int j = 0; j < values.length; j++) { - values[j] = rand.nextInt(values.length); - } - } - } - - public void read() { - double avg = 0; - for (int i = 0; i < readLoops; i++) { - double sum = 0; - for (int j = 0; j < values.length; j++) { - sum += values[j]; - } - sum /= values.length; - avg += sum; - } - avg /= readLoops; - number = avg; - } - } -} diff --git a/store-benchmark/src/main/java/org/gephi/graph/benchmark/NodeStoreBenchmark.java b/store-benchmark/src/main/java/org/gephi/graph/benchmark/NodeStoreBenchmark.java deleted file mode 100644 index 9e8131d6..00000000 --- a/store-benchmark/src/main/java/org/gephi/graph/benchmark/NodeStoreBenchmark.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.benchmark; - -import java.util.Iterator; -import java.util.List; - -import org.gephi.graph.api.Configuration; -import org.gephi.graph.api.Node; -import org.gephi.graph.impl.NodeImpl; -import org.gephi.graph.impl.NodeStore; - -/** - * - * @author mbastian, niteshbhargv - */ -public class NodeStoreBenchmark { - - private Object object; - - public Runnable iterateStore(final int nodes) { - final Configuration config = new Configuration(); - config.setEdgeIdType(Integer.class); - config.setNodeIdType(Integer.class); - final RandomGraph graph = new RandomGraph(nodes, 0, config).generate().commit(); - final NodeStore nodeStore = graph.getStore().getNodeStore(); - Runnable runnable = () -> { - Iterator m = nodeStore.iterator(); - for (; m.hasNext();) { - NodeImpl b = (NodeImpl) m.next(); - object = b; - } - }; - return runnable; - } - - public Runnable resetNodeStore(final int nodes) { - final Configuration config = new Configuration(); - config.setEdgeIdType(Integer.class); - config.setNodeIdType(Integer.class); - final RandomGraph graph = new RandomGraph(nodes, 0, config).generate().commit(); - final NodeStore nodeStore = graph.getStore().getNodeStore(); - final List nodeList = graph.getNodes(); - Runnable runnable = () -> { - for (Node n : nodeList) { - nodeStore.remove(n); - } - for (Node n : nodeList) { - nodeStore.add(n); - } - }; - return runnable; - } - - public Runnable pushStore(int nodes) { - final Configuration config = new Configuration(); - config.setEdgeIdType(Integer.class); - config.setNodeIdType(Integer.class); - final RandomGraph graph = new RandomGraph(nodes, 0, config).generate(); - final NodeStore nodeStore = graph.getStore().getNodeStore(); - final List nodeList = graph.getNodes(); - - Runnable runnable = () -> { - nodeStore.clear(); - for (Node n : nodeList) { - nodeStore.add(n); - } - }; - return runnable; - } -} diff --git a/store-benchmark/src/main/java/org/gephi/graph/benchmark/RandomGraph.java b/store-benchmark/src/main/java/org/gephi/graph/benchmark/RandomGraph.java deleted file mode 100644 index 82c10447..00000000 --- a/store-benchmark/src/main/java/org/gephi/graph/benchmark/RandomGraph.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.benchmark; - -import java.util.Random; - -import org.gephi.graph.api.Configuration; -import org.gephi.graph.api.Edge; -import org.gephi.graph.api.Node; -import org.gephi.graph.impl.NodeImpl; - -/** - * Generates directed connected random graph with wiring probability p - * - * @author Mathieu Bastian, Nitesh Bhargava - */ -public class RandomGraph extends Generator { - - protected final int numberOfNodes; - protected final int numberOfEdges; - protected final double wiringProbability; - - public RandomGraph(int n, double p) { - this(n, p, new Configuration()); - } - - public RandomGraph(int n, double p, Configuration config) { - super(config); - numberOfNodes = n; - numberOfEdges = (int)(n*(n-1)*p); - wiringProbability = p; - } - - public RandomGraph(int nodes, int edges) { - this(nodes, edges, new Configuration()); - } - - public RandomGraph(int nodes, int edges, Configuration confi) { - this(nodes, ((double)edges)/(nodes*(nodes-1)), confi); - } - - @Override - public RandomGraph generate() { - Random random = new Random(); - - for (int i = 0; i < numberOfNodes; i++) { - Node node = factory.newNode(i); - nodes.add(node); - } - - if (wiringProbability > 0) { - for (int i = 0; i < numberOfNodes - 1; i++) { - NodeImpl source = graphStore.getNode(i); - for (int j = i + 1; j < numberOfNodes; j++) { - NodeImpl target = graphStore.getNode(j); - - if (random.nextDouble() < wiringProbability && source != target) { - Edge edge = factory.newEdge(source, target, 0, true); - edges.add(edge); - } - } - } - } - return this; - } - - @Override - public RandomGraph commit() { - commitInner(); - return this; - } -} diff --git a/store-benchmark/src/main/java/org/gephi/nanobench/NanoBench.java b/store-benchmark/src/main/java/org/gephi/nanobench/NanoBench.java deleted file mode 100644 index 0c73ccc5..00000000 --- a/store-benchmark/src/main/java/org/gephi/nanobench/NanoBench.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.nanobench; - -import java.lang.management.ManagementFactory; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Lightweight CPU and memory benchmarking utility.

Inspired from nanobench - * (http://code.google.com/p/nanobench/) - * - * @author mbastian - */ -public class NanoBench { - - public static NanoBench create() { - return new NanoBench(); - } - private static final Logger logger = Logger.getLogger(NanoBench.class.getSimpleName()); - private int numberOfMeasurement = 50; - private int numberOfWarmUp = 0; - private List listeners; - - public NanoBench() { - listeners = new ArrayList<>(2); - listeners.add(new CPUMeasure(logger)); - listeners.add(new MemoryUsage(logger)); - } - - public NanoBench measurements(int numberOfMeasurement) { - this.numberOfMeasurement = numberOfMeasurement; - return this; - } - - public NanoBench warmUps(int numberOfWarmups) { - this.numberOfWarmUp = numberOfWarmups; - return this; - } - - public NanoBench cpuAndMemory() { - listeners = new ArrayList<>(2); - listeners.add(new CPUMeasure(logger)); - listeners.add(new MemoryUsage(logger)); - return this; - } - - public static Logger getLogger() { - return logger; - } - - public NanoBench cpuOnly() { - listeners = new ArrayList<>(1); - listeners.add(new CPUMeasure(logger)); - return this; - } - - public NanoBench memoryOnly() { - listeners = new ArrayList<>(1); - listeners.add(new MemoryUsage(logger)); - return this; - } - - public void measure(String label, Runnable task) { - MemoryUtil.restoreJvm(); - doWarmup(task); - MemoryUtil.restoreJvm(); - stress(); - doMeasure(label, task); - stress(); - MemoryUtil.restoreJvm(); - try { - Thread.sleep(1000); - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - static int[] arrayStress = new int[10000]; - - private void stress() { - int m = 0; - for (int j = 0; j < 100; j++) { - int dummy = 0; - for (int i = 1; i < arrayStress.length; i++) { - arrayStress[i] = (int) Math.round(Math.log(i)); - dummy += arrayStress[i - 1]; - } - m += dummy; - } - } - - private void doMeasure(String label, Runnable task) { - for (int i = 0; i < this.numberOfMeasurement; i++) { - TimeMeasureProxy tmp = new TimeMeasureProxy(new MeasureState(label, i, this.numberOfMeasurement), task, listeners); - tmp.run(); - } - } - - private void doWarmup(Runnable task) { - for (int i = 0; i < this.numberOfWarmUp; i++) { - TimeMeasureProxy tmp = new TimeMeasureProxy(new MeasureState("_warmup_", i, this.numberOfWarmUp), task, listeners); - tmp.run(); - } - } - - /** - * Decorated runnable which enables measurements. - */ - private static class TimeMeasureProxy implements Runnable { - - private MeasureState state; - private Runnable runnable; - private List listeners; - - public TimeMeasureProxy(MeasureState state, Runnable runnable, List listeners) { - super(); - this.state = state; - this.runnable = runnable; - this.listeners = listeners; - } - - @Override - public void run() { - this.state.startNow(); - this.runnable.run(); - this.state.endNow(); - if (!state.getLabel().equals("_warmup_")) { - notifyMeasurement(state); - } - } - - private void notifyMeasurement(MeasureState times) { - for (MeasureListener listener : this.listeners) { - listener.onMeasure(times); - } - } - } - - /** - * Interface for measure listeners. Measure listeners are called when a - * measurement is finished. - */ - private interface MeasureListener { - - void onMeasure(MeasureState state); - } - - /** - * Basic class to measure time spent in each measurement - */ - private static class MeasureState implements Comparable { - - private String label; - private long startTime; - private long endTime; - private long index; - private int measurement; - - public MeasureState(String label, long index, int measurement) { - super(); - this.label = label; - this.measurement = measurement; - this.index = index; - } - - public long getIndex() { - return index; - } - - public String getLabel() { - return label; - } - - public long getStartTime() { - return startTime; - } - - public long getEndTime() { - return endTime; - } - - public long getMeasurements() { - return measurement; - } - - public long getMeasureTime() { - return endTime - startTime; - } - - public void startNow() { - this.startTime = System.nanoTime(); - } - - public void endNow() { - this.endTime = System.nanoTime(); - } - - @Override - public int compareTo(MeasureState another) { - if (this.startTime > another.startTime) { - return -1; - } else if (this.startTime < another.startTime) { - return 1; - } else { - return 0; - } - } - } - - /** - * CPU time listener to calculate the average time spent in a measurement. - *

The listener is called at the end of each measurement and collect the - * time spent from the - * MeasureState instance. At the last measurement it shows the - * average time spent, the total time and the number of measurement per - * seconds. - */ - private static class CPUMeasure implements MeasureListener { - - private static final double BY_SECONDS = 1000000000.0; - private final Logger log; - private static final DecimalFormat decimalFormat = new DecimalFormat("#,##0.0000"); - private static final DecimalFormat integerFormat = new DecimalFormat("#,##0.0"); - private int count = 0; - private long timeUsed = 0; - - public CPUMeasure(Logger logger) { - this.log = logger; - } - - @Override - public void onMeasure(MeasureState state) { - count++; - outputMeasureInfo(state); - } - - private void outputMeasureInfo(MeasureState state) { - timeUsed += state.getMeasureTime(); - - if (isEnd(state)) { - long total = timeUsed; - - StringBuilder sb = new StringBuilder("\n"); - sb.append(state.getLabel()).append("\t").append("avg: ").append( - decimalFormat.format(total / state.getMeasurements() / 1000000.0)) - .append(" ms\t").append("total: ").append( - integerFormat.format(total / 1000000000.0)).append(" s\t").append( - " tps: ").append( - integerFormat.format(state.getMeasurements() - / (total / BY_SECONDS))).append("\t") - .append("running: ").append(count) - .append(" times"); - count = 0; - timeUsed = 0; - if (!state.getLabel().equals("_warmup_")) { - log.info(sb.toString()); - } - } - } - - private boolean isEnd(MeasureState state) { - return count == state.getMeasurements(); - } - } - - /** - * Memory usage listener to calculate the average memory usage.

The - * listener is called after each measurement and perform a full GC and - * calculate free memory. At the last measurement it shows the average - * memory usage. - */ - private static class MemoryUsage implements MeasureListener { - - private final Logger log; - private static final DecimalFormat integerFormat = new DecimalFormat("#,##0.000"); - private int count = 0; - private long memoryUsed = 0; - - public MemoryUsage(Logger logger) { - this.log = logger; - } - - @Override - public void onMeasure(MeasureState state) { - count++; - outputMeasureInfo(state); - } - - private void outputMeasureInfo(MeasureState state) { - MemoryUtil.restoreJvm(); - memoryUsed += MemoryUtil.memoryUsed(); - - if (isEnd(state)) { - StringBuilder sb = new StringBuilder("\n"); - sb.append("memory-usage: ").append(state.getLabel()).append("\t") - .append(format((memoryUsed / count) / (1024.0 * 1024.0))).append( - " Mb\n"); - count = 0; - memoryUsed = 0; - - if (!state.getLabel().equals("_warmup_")) { - log.info(sb.toString()); - } - } - } - - private String format(double value) { - return integerFormat.format(value); - } - - private boolean isEnd(MeasureState state) { - return count == state.getMeasurements(); - } - } - - /** - * Utility memory class to perform GC and calculate memory usage - */ - public static class MemoryUtil { - - /** - * Call GC until no more memory can be freed - */ - public static void restoreJvm() { - int maxRestoreJvmLoops = 10; - long memUsedPrev = memoryUsed(); - for (int i = 0; i < maxRestoreJvmLoops; i++) { - System.runFinalization(); - System.gc(); - - long memUsedNow = memoryUsed(); - // break early if have no more finalization and get constant mem used - if ((ManagementFactory.getMemoryMXBean() - .getObjectPendingFinalizationCount() == 0) - && (memUsedNow >= memUsedPrev)) { - break; - } else { - memUsedPrev = memUsedNow; - } - } - } - - /** - * Return the memory used in bytes - * - * @return heap memory used in bytes - */ - public static long memoryUsed() { - Runtime rt = Runtime.getRuntime(); - return rt.totalMemory() - rt.freeMemory(); - } - } -} diff --git a/store-benchmark/src/test/java/org/gephi/graph/benchmark/ControlBenchmarkTest.java b/store-benchmark/src/test/java/org/gephi/graph/benchmark/ControlBenchmarkTest.java deleted file mode 100644 index 2cc93cc6..00000000 --- a/store-benchmark/src/test/java/org/gephi/graph/benchmark/ControlBenchmarkTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.benchmark; - -import java.util.logging.Level; -import java.util.logging.Logger; -import org.gephi.graph.benchmark.util.ReporterHandler; -import org.gephi.nanobench.NanoBench; -import org.testng.Reporter; -import org.testng.annotations.BeforeSuite; -import org.testng.annotations.Test; - -public class ControlBenchmarkTest { - - @BeforeSuite - public void setUp() { - Logger logger = NanoBench.getLogger(); - logger.setUseParentHandlers(false); - logger.setLevel(Level.INFO); - logger.addHandler(new ReporterHandler()); - Reporter.setEscapeHtml(false); - } - - @Test - public void testControl() { - Runnable runnable = new Runnable() { - final int[] array = new int[10000000]; - int m = 0; - - @Override - public void run() { - int dummy = 0; - for (int doNotIgnoreMe : array) { - dummy += doNotIgnoreMe; - } - m += dummy; - } - }; - NanoBench.create().measure("control", runnable); - } -} diff --git a/store-benchmark/src/test/java/org/gephi/graph/benchmark/EdgeStoreBenchmarkTest.java b/store-benchmark/src/test/java/org/gephi/graph/benchmark/EdgeStoreBenchmarkTest.java deleted file mode 100644 index 6306a0c2..00000000 --- a/store-benchmark/src/test/java/org/gephi/graph/benchmark/EdgeStoreBenchmarkTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.benchmark; - -import org.gephi.nanobench.NanoBench; -import org.testng.annotations.Test; - -public class EdgeStoreBenchmarkTest { - - @Test - public void testPushStore() { - int[] n = {100, 1000, 5000}; - double[] p = {0.01, 0.1, 0.3}; - for (int nodes : n) { - for (double prob : p) { - int edges = (int) (nodes * (nodes - 1) * prob); - NanoBench.create().measurements(2).measure("push edge store nodes=" + nodes + " edges=" + edges, new EdgeStoreBenchmark().pushEdgeStore(nodes, prob)); - } - } - } - - @Test - public void testIterateStore() { - int[] n = {100, 1000, 5000}; - double[] p = {0.01, 0.1, 0.3}; - for (int nodes : n) { - for (double prob : p) { - int edges = (int) (nodes * (nodes - 1) * prob); - NanoBench.create().measurements(2).measure("iterate edge store nodes=" + nodes + " edges=" + edges, new EdgeStoreBenchmark().iterateEdgeStore(nodes, prob)); - } - } - } - - @Test - public void testIterateOutNeighbors() { - int[] n = {100, 1000, 5000}; - double[] p = {0.01, 0.1, 0.3}; - for (int nodes : n) { - for (double prob : p) { - int edges = (int) (nodes * (nodes - 1) * prob); - NanoBench.create().measurements(2).measure("iterate neighbors list out nodes=" + nodes + " edges=" + edges, new EdgeStoreBenchmark().iterateEdgeStoreNeighborsOut(nodes, prob)); - } - } - } - - @Test - public void testIterateInOutNeighbors() { - int[] n = {100, 1000, 5000}; - double[] p = {0.01, 0.1, 0.3}; - for (int nodes : n) { - for (double prob : p) { - int edges = (int) (nodes * (nodes - 1) * prob); - NanoBench.create().measurements(2).measure("iterate neighbors list in&out nodes=" + nodes + " edges=" + edges, new EdgeStoreBenchmark().iterateEdgeStoreNeighborsInOut(nodes, prob)); - } - } - } - - @Test - public void testResetEdgeStore() { - int[] n = {100, 1000, 5000}; - double[] p = {0.01, 0.1, 0.3}; - for (int nodes : n) { - for (double prob : p) { - int edges = (int) (nodes * (nodes - 1) * prob); - NanoBench.create().measurements(2).measure("reset edge store nodes=" + nodes + " edges=" + edges, new EdgeStoreBenchmark().resetEdgeStore(nodes, prob)); - } - } - } -} diff --git a/store-benchmark/src/test/java/org/gephi/graph/benchmark/NodeStoreBenchmarkTest.java b/store-benchmark/src/test/java/org/gephi/graph/benchmark/NodeStoreBenchmarkTest.java deleted file mode 100644 index ef722555..00000000 --- a/store-benchmark/src/test/java/org/gephi/graph/benchmark/NodeStoreBenchmarkTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.benchmark; - -import org.gephi.nanobench.NanoBench; -import org.testng.annotations.Test; - -public class NodeStoreBenchmarkTest { - - @Test - public void testPushStore() { - int[] n = {100, 1000, 10000, 100000}; - for (int nodes : n) { - NanoBench.create().measurements(10).measure("push node store " + nodes, new NodeStoreBenchmark().pushStore(nodes)); - } - } - - @Test - public void testIterateStore() { - int[] n = {100, 1000, 10000, 100000}; - for (int nodes : n) { - NanoBench.create().cpuOnly().measurements(10).measure("iterate node store " + nodes, new NodeStoreBenchmark().iterateStore(nodes)); - } - } - - @Test - public void testResetNodeStore() { - int[] n = {100, 1000, 10000, 100000}; - for (int nodes : n) { - NanoBench.create().measurements(10).measure("reset node store "+nodes, new NodeStoreBenchmark().resetNodeStore(nodes)); - } - } -} diff --git a/store-benchmark/src/test/java/org/gephi/graph/benchmark/util/ReporterHandler.java b/store-benchmark/src/test/java/org/gephi/graph/benchmark/util/ReporterHandler.java deleted file mode 100644 index 9ec03293..00000000 --- a/store-benchmark/src/test/java/org/gephi/graph/benchmark/util/ReporterHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.benchmark.util; - -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import org.testng.Reporter; - -/** - * Bridge between Java Logging API and TestNG's Reporter - * - * @author mbastian - */ -public class ReporterHandler extends Handler { - - @Override - public void publish(LogRecord record) { - String prefix = ""; - if (record.getLevel().equals(Level.INFO)) { - prefix = "[INFO] "; - } else if (record.getLevel().equals(Level.WARNING)) { - prefix = "[WARN] "; - } else if (record.getLevel().equals(Level.SEVERE)) { - prefix = "[SEVERE] "; - } - Reporter.log(prefix + record.getMessage() + "
", true); - } - - @Override - public void flush() { - } - - @Override - public void close() throws SecurityException { - } -} diff --git a/store/src/main/java/org/gephi/graph/api/Configuration.java b/store/src/main/java/org/gephi/graph/api/Configuration.java deleted file mode 100644 index d14223cc..00000000 --- a/store/src/main/java/org/gephi/graph/api/Configuration.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.api; - -import org.gephi.graph.api.types.IntervalDoubleMap; -import org.gephi.graph.api.types.TimestampDoubleMap; -import org.gephi.graph.impl.GraphStoreConfiguration; - -/** - * Global configuration set at initialization. - *

- * This class can be passed as a parameter to - * {@link GraphModel.Factory#newInstance(org.gephi.graph.api.Configuration)} to - * create a GraphModel with custom configuration. - *

- * Note that setting configurations after the GraphModel has been - * created won't have any effect. - *

- * By default, both node and edge id types are String.class and the - * time representation is TIMESTAMP. - * - * @see GraphModel - */ -public class Configuration { - - private Class nodeIdType; - private Class edgeIdType; - private Class edgeLabelType; - private Class edgeWeightType; - private TimeRepresentation timeRepresentation; - private Boolean edgeWeightColumn; - - /** - * Default constructor. - */ - public Configuration() { - nodeIdType = GraphStoreConfiguration.DEFAULT_NODE_ID_TYPE; - edgeIdType = GraphStoreConfiguration.DEFAULT_EDGE_ID_TYPE; - edgeLabelType = GraphStoreConfiguration.DEFAULT_EDGE_LABEL_TYPE; - edgeWeightType = GraphStoreConfiguration.DEFAULT_EDGE_WEIGHT_TYPE; - timeRepresentation = GraphStoreConfiguration.DEFAULT_TIME_REPRESENTATION; - edgeWeightColumn = true; - } - - /** - * Returns the node id type. - * - * @return node id type - */ - public Class getNodeIdType() { - return nodeIdType; - } - - /** - * Sets the node id type. - *

- * Only simple types such as primitives, wrappers and String are supported. - * - * @param nodeIdType node id type - * @throws IllegalArgumentException if the type isn't supported - */ - public void setNodeIdType(Class nodeIdType) { - if (!AttributeUtils.isSimpleType(nodeIdType)) { - throw new IllegalArgumentException("Unsupported type " + nodeIdType.getClass().getCanonicalName()); - } - this.nodeIdType = nodeIdType; - } - - /** - * Returns the edge id type. - * - * @return edge id type - */ - public Class getEdgeIdType() { - return edgeIdType; - } - - /** - * Sets the edge id type. - *

- * Only simple types such as primitives, wrappers and String are supported. - * - * @param edgeIdType edge id type - * @throws IllegalArgumentException if the type isn't supported - */ - public void setEdgeIdType(Class edgeIdType) { - if (!AttributeUtils.isSimpleType(edgeIdType)) { - throw new IllegalArgumentException("Unsupported type " + edgeIdType.getClass().getCanonicalName()); - } - this.edgeIdType = edgeIdType; - } - - /** - * Returns the edge label type. - * - * @return edge label type - */ - public Class getEdgeLabelType() { - return edgeLabelType; - } - - /** - * Sets the edge label type. - * - * @param edgeLabelType edge label type - * @throws IllegalArgumentException if the type isn't supported - */ - public void setEdgeLabelType(Class edgeLabelType) { - if (!AttributeUtils.isSimpleType(edgeLabelType)) { - throw new IllegalArgumentException("Unsupported type " + edgeLabelType.getClass().getCanonicalName()); - } - this.edgeLabelType = edgeLabelType; - } - - /** - * Returns the edge weight type. - * - * @return edge weight type - */ - public Class getEdgeWeightType() { - return edgeWeightType; - } - - /** - * Sets the edge weight type. - * - * @param edgeWeightType edge weight type - * @throws IllegalArgumentException if the type isn't supported - */ - public void setEdgeWeightType(Class edgeWeightType) { - if (Double.class.equals(edgeWeightType) || TimestampDoubleMap.class.equals(edgeWeightType) || IntervalDoubleMap.class - .equals(edgeWeightType)) { - this.edgeWeightType = edgeWeightType; - } else { - throw new IllegalArgumentException("Unsupported type " + edgeWeightType.getClass().getCanonicalName()); - } - } - - /** - * Returns the time representation. - * - * @return time representation - */ - public TimeRepresentation getTimeRepresentation() { - return timeRepresentation; - } - - /** - * Sets the time representation. - * - * @param timeRepresentation time representation - */ - public void setTimeRepresentation(TimeRepresentation timeRepresentation) { - if (timeRepresentation == null) { - throw new IllegalArgumentException("timeRepresentation cannot be null"); - } - this.timeRepresentation = timeRepresentation; - } - - /** - * Returns whether an edge weight column is created. - * - * @return edge weight column - */ - public Boolean getEdgeWeightColumn() { - return edgeWeightColumn; - } - - /** - * Sets whether to create an edge weight column. - * - * @param edgeWeightColumn edge weight column - */ - public void setEdgeWeightColumn(Boolean edgeWeightColumn) { - this.edgeWeightColumn = edgeWeightColumn; - } - - /** - * Copy this configuration. - * - * @return a copy of this configuration - */ - public Configuration copy() { - Configuration copy = new Configuration(); - copy.nodeIdType = nodeIdType; - copy.edgeIdType = edgeIdType; - copy.edgeLabelType = edgeLabelType; - copy.edgeWeightType = edgeWeightType; - copy.timeRepresentation = timeRepresentation; - copy.edgeWeightColumn = edgeWeightColumn; - return copy; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 19 * hash + (this.nodeIdType != null ? this.nodeIdType.hashCode() : 0); - hash = 19 * hash + (this.edgeIdType != null ? this.edgeIdType.hashCode() : 0); - hash = 19 * hash + (this.edgeLabelType != null ? this.edgeLabelType.hashCode() : 0); - hash = 19 * hash + (this.edgeWeightType != null ? this.edgeWeightType.hashCode() : 0); - hash = 19 * hash + (this.timeRepresentation != null ? this.timeRepresentation.hashCode() : 0); - hash = 19 * hash + (this.edgeWeightColumn != null ? this.edgeWeightColumn.hashCode() : 0); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final Configuration other = (Configuration) obj; - if (this.nodeIdType != other.nodeIdType && (this.nodeIdType == null || !this.nodeIdType - .equals(other.nodeIdType))) { - return false; - } - if (this.edgeIdType != other.edgeIdType && (this.edgeIdType == null || !this.edgeIdType - .equals(other.edgeIdType))) { - return false; - } - if (this.edgeLabelType != other.edgeLabelType && (this.edgeLabelType == null || !this.edgeLabelType - .equals(other.edgeLabelType))) { - return false; - } - if (this.edgeWeightType != other.edgeWeightType && (this.edgeWeightType == null || !this.edgeWeightType - .equals(other.edgeWeightType))) { - return false; - } - if (this.timeRepresentation != other.timeRepresentation && (this.timeRepresentation == null || !this.timeRepresentation - .equals(other.timeRepresentation))) { - return false; - } - if (this.edgeWeightColumn != other.edgeWeightColumn && (this.edgeWeightColumn == null || !this.edgeWeightColumn - .equals(other.edgeWeightColumn))) { - return false; - } - return true; - } -} diff --git a/store/src/main/java/org/gephi/graph/api/Rect2D.java b/store/src/main/java/org/gephi/graph/api/Rect2D.java deleted file mode 100644 index ad02f078..00000000 --- a/store/src/main/java/org/gephi/graph/api/Rect2D.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.gephi.graph.api; - -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.NumberFormat; -import java.util.Locale; - -/** - * Represents a 2D axis-aligned immutable rectangle. - * - * @author Eduardo Ramos - */ -public class Rect2D { - - public final float minX, minY; - public final float maxX, maxY; - - /** - * Create a new {@link Rect2D} as a copy of the given source . - * - * @param source the {@link Rect2D} to copy from - */ - public Rect2D(Rect2D source) { - this.minX = source.minX; - this.minY = source.minY; - this.maxX = source.maxX; - this.maxY = source.maxY; - } - - /** - * Create a new {@link Rect2D} with the given minimum and maximum corner - * coordinates. - * - * @param minX the x coordinate of the minimum corner - * @param minY the y coordinate of the minimum corner - * @param maxX the x coordinate of the maximum corner - * @param maxY the y coordinate of the maximum corner - */ - public Rect2D(float minX, float minY, float maxX, float maxY) { - if (minX > maxX) { - throw new IllegalArgumentException("minX > maxX"); - } - - if (minY > maxY) { - throw new IllegalArgumentException("minX > maxX"); - } - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - public float width() { - return maxX - minX; - } - - public float height() { - return maxY - minY; - } - - public float[] center() { - return new float[] { (maxX + minX) / 2, (maxY + minY) / 2 }; - } - - public float radius() { - float width = width(); - float height = height(); - return (float) Math.sqrt(width * width + height * height) / 2; - } - - private static final DecimalFormat FORMAT = new DecimalFormat("0.###", - DecimalFormatSymbols.getInstance(Locale.ENGLISH)); - - @Override - public String toString() { - return toString(FORMAT); - } - - public String toString(NumberFormat formatter) { - return "(" + formatter.format(minX) + " " + formatter.format(minY) + ") < " + "(" + formatter.format(maxX) + " " + formatter - .format(maxY) + ")"; - } - - public boolean contains(Rect2D rect) { - if (rect == this) { - return true; - } - - return contains(rect.minX, rect.minY, rect.maxX, rect.maxY); - } - - public boolean intersects(Rect2D rect) { - if (rect == this) { - return true; - } - - return intersects(rect.minX, rect.minY, rect.maxX, rect.maxY); - } - - public boolean contains(float minX, float minY, float maxX, float maxY) { - return this.minX <= minX && this.minY <= minY && this.maxX >= maxX && this.maxY >= maxY; - } - - public boolean intersects(float minX, float minY, float maxX, float maxY) { - return this.minX <= maxX && minX <= this.maxX && this.maxY >= minY && maxY >= this.minY; - } -} diff --git a/store/src/main/java/org/gephi/graph/api/SpatialContext.java b/store/src/main/java/org/gephi/graph/api/SpatialContext.java deleted file mode 100644 index fbd0266d..00000000 --- a/store/src/main/java/org/gephi/graph/api/SpatialContext.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.gephi.graph.api; - -import java.util.function.Consumer; - -/** - * Object to query the nodes and edges of the graph in a spatial context. - * - * @author Eduardo Ramos - */ -public interface SpatialContext { - - NodeIterable getNodesInArea(Rect2D rect); - - void getNodesInArea(Rect2D rect, Consumer callback); - - EdgeIterable getEdgesInArea(Rect2D rect); - - void getEdgesInArea(Rect2D rect, Consumer callback); -} diff --git a/store/src/main/java/org/gephi/graph/api/package.html b/store/src/main/java/org/gephi/graph/api/package.html deleted file mode 100644 index 4b6171f6..00000000 --- a/store/src/main/java/org/gephi/graph/api/package.html +++ /dev/null @@ -1,3 +0,0 @@ - - Complete API description, where GraphModel is the entry point. - diff --git a/store/src/main/java/org/gephi/graph/api/types/package.html b/store/src/main/java/org/gephi/graph/api/types/package.html deleted file mode 100644 index 75e3f2a5..00000000 --- a/store/src/main/java/org/gephi/graph/api/types/package.html +++ /dev/null @@ -1,3 +0,0 @@ - - Custom types the API supports, in addition of primitive and arrays. - diff --git a/store/src/main/java/org/gephi/graph/impl/EdgesQuadTree.java b/store/src/main/java/org/gephi/graph/impl/EdgesQuadTree.java deleted file mode 100644 index a531dca8..00000000 --- a/store/src/main/java/org/gephi/graph/impl/EdgesQuadTree.java +++ /dev/null @@ -1,666 +0,0 @@ -package org.gephi.graph.impl; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; -import org.gephi.graph.api.Edge; -import org.gephi.graph.api.EdgeIterable; -import org.gephi.graph.api.Rect2D; - -/** - * Adapted from https://bitbucket.org/C3/quadtree/wiki/Home - * - * TODO: unit tests!! - * TODO: almost the same as NodesQuadTree, maybe generate code with templating-maven-plugin - * @author Eduardo Ramos - */ -public class EdgesQuadTree { - - private static final int MAX_OBJECTS_PER_NODE = 2; - private static final int MAX_LEVELS = 16; - - private final GraphLock lock = new GraphLock(); - private Map wrappedDictionary = new LinkedHashMap<>(); - - private QuadTreeNode quadTreeRoot; - - public EdgesQuadTree(Rect2D rect) { - quadTreeRoot = new QuadTreeNode(rect); - } - - public EdgesQuadTree(float dimensionMax) { - this(-dimensionMax, -dimensionMax, dimensionMax, dimensionMax); - } - - public EdgesQuadTree(float minX, float minY, float maxX, float maxY) { - quadTreeRoot = new QuadTreeNode(new Rect2D(minX, minY, maxX, maxY)); - } - - public Rect2D quadRect() { - return quadTreeRoot.quadRect(); - } - - public EdgeIterable getEdges(Rect2D rect) { - return quadTreeRoot.getEdges(rect); - } - - public void getEdges(Rect2D rect, Consumer callback) { - quadTreeRoot.getEdges(rect, callback); - } - - public EdgeIterable getEdges(float minX, float minY, float maxX, float maxY) { - return quadTreeRoot.getEdges(new Rect2D(minX, minY, maxX, maxY)); - } - - public void getEdges(float minX, float minY, float maxX, float maxY, Consumer callback) { - quadTreeRoot.getEdges(new Rect2D(minX, minY, maxX, maxY), callback); - } - - public EdgeIterable getAllEdges() { - return quadTreeRoot.getAllEdges(); - } - - public void getAllEdges(Consumer callback) { - quadTreeRoot.getAllEdges(callback); - } - - public boolean updateEdge(Edge item, float minX, float minY, float maxX, float maxY) { - writeLock(); - try { - final QuadTreeObject obj = wrappedDictionary.get(item); - if (obj != null) { - obj.updateItemCoords(minX, minY, maxX, maxY); - quadTreeRoot.update(obj); - return true; - } else { - return false; - } - } finally { - writeUnlock(); - } - } - - public boolean addEdge(Edge item, float minX, float minY, float maxX, float maxY) { - writeLock(); - try { - if (!containsEdge(item)) { - final QuadTreeObject wrappedObject = new QuadTreeObject(item, minX, minY, maxX, maxY); - wrappedDictionary.put(item, wrappedObject); - quadTreeRoot.insert(wrappedObject); - return true; - } else { - return false; - } - } finally { - writeUnlock(); - } - } - - public void clear() { - writeLock(); - try { - wrappedDictionary.clear(); - quadTreeRoot.clear(); - } finally { - writeUnlock(); - } - } - - public boolean containsEdge(Edge item) { - readLock(); - try { - return wrappedDictionary.containsKey(item); - } finally { - readUnlock(); - } - } - - public int count() { - readLock(); - try { - return wrappedDictionary.size(); - } finally { - readUnlock(); - } - } - - public boolean removeEdge(Edge item) { - writeLock(); - try { - final QuadTreeObject obj = wrappedDictionary.get(item); - if (obj != null) { - quadTreeRoot.delete(obj, true); - wrappedDictionary.remove(item); - return true; - } else { - return false; - } - } finally { - writeUnlock(); - } - } - - public void readLock() { - if (lock != null) { - lock.readLock(); - } - } - - public void readUnlock() { - if (lock != null) { - lock.readUnlock(); - } - } - - public void writeLock() { - if (lock != null) { - lock.writeLock(); - } - } - - public void writeUnlock() { - if (lock != null) { - lock.writeUnlock(); - } - } - - private class QuadTreeObject { - - private final Edge data; - private float minX, minY, maxX, maxY; - - private QuadTreeNode owner; - - public QuadTreeObject(Edge data, float minX, float minY, float maxX, float maxY) { - this.data = data; - updateItemCoords(minX, minY, maxX, maxY); - } - - private void updateItemCoords(float minX, float minY, float maxX, float maxY) { - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - } - - private class QuadTreeNode { - - private Set objects = null; - private final Rect2D rect; // The area this QuadTree represents - - private final QuadTreeNode parent; // The parent of this quad - private final int level; - - private QuadTreeNode childTL = null; // Top Left Child - private QuadTreeNode childTR = null; // Top Right Child - private QuadTreeNode childBL = null; // Bottom Left Child - private QuadTreeNode childBR = null; // Bottom Right Child - - public Rect2D quadRect() { - return rect; - } - - public QuadTreeNode topLeftChild() { - return childTL; - } - - public QuadTreeNode topRightChild() { - return childTR; - } - - public QuadTreeNode bottomLeftChild() { - return childBL; - } - - public QuadTreeNode bottomRightChild() { - return childBR; - } - - public QuadTreeNode parent() { - return parent; - } - - public int count() { - return objectCount(); - } - - public boolean isEmptyLeaf() { - return count() == 0 && childTL == null; - } - - public QuadTreeNode(Rect2D rect) { - this(null, 0, rect); - } - - private QuadTreeNode(QuadTreeNode parent, int level, Rect2D rect) { - this.level = level; - this.rect = rect; - this.parent = parent; - } - - private void add(QuadTreeObject item) { - if (objects == null) { - objects = new LinkedHashSet<>(); - } - - item.owner = this; - objects.add(item); - } - - private void remove(QuadTreeObject item) { - if (objects != null) { - objects.remove(item); - } - } - - private int objectCount() { - int count = 0; - - // add the objects at this level - if (objects != null) { - count += objects.size(); - } - - // add the objects that are contained in the children - if (childTL != null) { - count += childTL.objectCount(); - count += childTR.objectCount(); - count += childBL.objectCount(); - count += childBR.objectCount(); - } - - return count; - } - - private void subdivide() { - // We've reached capacity, subdivide... - final float minX = rect.minX; - final float halfX = (rect.minX + rect.maxX) / 2; - final float maxX = rect.maxX; - - final float minY = rect.minY; - final float halfY = (rect.minY + rect.maxY) / 2; - final float maxY = rect.maxY; - - childTL = new QuadTreeNode(this, level + 1, new Rect2D(minX, minY, halfX, halfY)); - childTR = new QuadTreeNode(this, level + 1, new Rect2D(halfX, minY, maxX, halfY)); - childBL = new QuadTreeNode(this, level + 1, new Rect2D(minX, halfY, halfX, maxY)); - childBR = new QuadTreeNode(this, level + 1, new Rect2D(halfX, halfY, maxX, maxY)); - - // If they're completely contained by the quad, bump objects down - final Iterator iterator = objects.iterator(); - while (iterator.hasNext()) { - QuadTreeObject obj = iterator.next(); - QuadTreeNode destTree = getDestinationTree(obj); - if (destTree != this) { - // Insert to the appropriate tree, remove the object, and - // back up one in the loop - destTree.insert(obj); - - iterator.remove(); - } - } - } - - private QuadTreeNode getDestinationTree(QuadTreeObject item) { - // If a child can't contain an object, it will live in this Quad - final QuadTreeNode destTree; - - final float minX = item.minX; - final float minY = item.minY; - final float maxX = item.maxX; - final float maxY = item.maxY; - - if (childTL.quadRect().contains(minX, minY, maxX, maxY)) { - destTree = childTL; - } else if (childTR.quadRect().contains(minX, minY, maxX, maxY)) { - destTree = childTR; - } else if (childBL.quadRect().contains(minX, minY, maxX, maxY)) { - destTree = childBL; - } else if (childBR.quadRect().contains(minX, minY, maxX, maxY)) { - destTree = childBR; - } else { - destTree = this; - } - - return destTree; - } - - private void relocate(QuadTreeObject item) { - // Are we still inside our parent? - if (quadRect().contains(item.minX, item.minY, item.maxX, item.maxY)) { - // Good, have we moved inside any of our children? - if (childTL != null) { - QuadTreeNode dest = getDestinationTree(item); - if (item.owner != dest) { - // Delete the item from this quad and add it to our - // child - // Note: Do NOT clean during this call, it can - // potentially delete our destination quad - QuadTreeNode formerOwner = item.owner; - delete(item, false); - dest.insert(item); - - // Clean up ourselves - formerOwner.cleanUpwards(); - } - } - } else { - // We don't fit here anymore, move up, if we can - if (parent != null) { - parent.relocate(item); - } - } - } - - private void cleanUpwards() { - if (childTL != null) { - // If all the children are empty leaves, delete all the children - if (childTL.isEmptyLeaf() && childTR.isEmptyLeaf() && childBL.isEmptyLeaf() && childBR.isEmptyLeaf()) { - childTL = null; - childTR = null; - childBL = null; - childBR = null; - - if (parent != null && count() == 0) { - parent.cleanUpwards(); - } - } - } else { - // I could be one of 4 empty leaves, tell my parent to clean up - if (parent != null && count() == 0) { - parent.cleanUpwards(); - } - } - } - - private void clear() { - // clear out the children, if we have any - if (childTL != null) { - childTL.clear(); - childTR.clear(); - childBL.clear(); - childBR.clear(); - } - - // clear any objects at this level - if (objects != null) { - objects.clear(); - objects = null; - } - - // Set the children to null - childTL = null; - childTR = null; - childBL = null; - childBR = null; - } - - private void delete(QuadTreeObject item, boolean clean) { - if (item.owner != null) { - if (item.owner == this) { - remove(item); - if (clean) { - cleanUpwards(); - } - } else { - item.owner.delete(item, clean); - } - } - } - - private void insert(QuadTreeObject item) { - // If this quad doesn't contain the items rectangle, do nothing, - // unless we are the root - if (!rect.contains(item.minX, item.minY, item.maxX, item.maxY)) { - if (parent == null) { - // This object is outside of the QuadTreeXNA bounds, we - // should add it at the root level - add(item); - } else { - throw new IllegalStateException( - "We are not the root, and this object doesn't fit here. How did we get here?"); - } - } - - if (objects == null || (childTL == null && (level >= MAX_LEVELS || objects.size() + 1 <= MAX_OBJECTS_PER_NODE))) { - // If there's room to add the object, just add it - add(item); - } else { - // No quads, create them and bump objects down where appropriate - if (childTL == null) { - subdivide(); - } - - // Find out which tree this object should go in and add it there - final QuadTreeNode destTree = getDestinationTree(item); - if (destTree == this) { - add(item); - } else { - destTree.insert(item); - } - } - } - - private EdgeIterable getEdges(Rect2D searchRect) { - return new QuadTreeEdgesIterable(searchRect); - } - - private EdgeIterable getAllEdges() { - return new QuadTreeEdgesIterable(null); - } - - private void getEdges(Rect2D searchRect, Consumer callback) { - if (searchRect.contains(this.rect)) { - this.getAllEdges(callback); - } else if (searchRect.intersects(this.rect)) { - if (objects != null && !objects.isEmpty()) { - for (QuadTreeObject obj : objects) { - if (searchRect.intersects(obj.minX, obj.minY, obj.maxX, obj.maxY)) { - callback.accept(obj.data); - } - } - } - - if (childTL != null) { - childTL.getEdges(searchRect, callback); - childTR.getEdges(searchRect, callback); - childBL.getEdges(searchRect, callback); - childBR.getEdges(searchRect, callback); - } - } - } - - private void getAllEdges(Consumer callback) { - if (objects != null && !objects.isEmpty()) { - for (QuadTreeObject obj : objects) { - callback.accept(obj.data); - } - } - - if (childTL != null) { - childTL.getAllEdges(callback); - childTR.getAllEdges(callback); - childBL.getAllEdges(callback); - childBR.getAllEdges(callback); - } - } - - private void update(QuadTreeObject item) { - if (item.owner != null) { - item.owner.relocate(item); - } else { - relocate(item); - } - } - - public void toString(StringBuilder sb) { - for (int i = 0; i < level; i++) { - sb.append(" "); - } - sb.append(rect.toString()).append('\n'); - - if (objects != null) { - for (QuadTreeObject object : objects) { - for (int i = 0; i <= level; i++) { - sb.append(" "); - } - - sb.append(object.data.getId()).append('\n'); - } - } - - if (childTL != null) { - childTL.toString(sb); - childTR.toString(sb); - childBL.toString(sb); - childBR.toString(sb); - } - } - } - - private class QuadTreeEdgesIterable implements EdgeIterable { - - private final Rect2D searchRect; - - public QuadTreeEdgesIterable(Rect2D searchRect) { - this.searchRect = searchRect; - } - - @Override - public Iterator iterator() { - return new QuadTreeEdgesIterator(quadTreeRoot, searchRect); - } - - @Override - public Edge[] toArray() { - final Collection collection = toCollection(); - return collection.toArray(new Edge[collection.size()]); - } - - @Override - public Collection toCollection() { - final List list = new ArrayList<>(); - - final Iterator iterator = iterator(); - while (iterator.hasNext()) { - list.add(iterator.next()); - } - - return list; - } - - @Override - public void doBreak() { - readUnlock(); - } - - } - - private class QuadTreeEdgesIterator implements Iterator { - - private final Rect2D searchRect; - private final Deque nodesStack = new ArrayDeque<>(); - private final Deque fullyContainedStack = new ArrayDeque<>(); - - // Current: - private Iterator currentIterator; - private boolean currentFullyContained = false; - private boolean finished = false; - - private QuadTreeObject next; - - public QuadTreeEdgesIterator(QuadTreeNode root, Rect2D searchRect) { - this.searchRect = searchRect; - - readLock(); - - // Null rect means get all - currentFullyContained = searchRect == null; - - // We always add the root and don't test for the root being fully - // contained, to correctly handle the case of nodes out of the quad - // tree bounds - addChildrenToVisit(root, currentFullyContained); - currentIterator = root.objects != null ? root.objects.iterator() : null; - } - - private void addChildrenToVisit(QuadTreeNode quadTreeNode, boolean fullyContained) { - if (quadTreeNode.childTL != null) { - nodesStack.push(quadTreeNode.childBR); - nodesStack.push(quadTreeNode.childBL); - nodesStack.push(quadTreeNode.childTR); - nodesStack.push(quadTreeNode.childTL); - - fullyContainedStack.push(fullyContained); - fullyContainedStack.push(fullyContained); - fullyContainedStack.push(fullyContained); - fullyContainedStack.push(fullyContained); - } - } - - @Override - public boolean hasNext() { - if (finished) { - return false; - } - - if (next != null) { - return true; - } - - while (currentIterator != null || !nodesStack.isEmpty()) { - if (currentIterator != null) { - while (currentIterator.hasNext()) { - final QuadTreeObject elem = currentIterator.next(); - - if (currentFullyContained || searchRect.intersects(elem.minX, elem.minY, elem.maxX, elem.maxY)) { - next = elem; - return true; - } - } - - currentIterator = null; - } else { - final QuadTreeNode pointer = nodesStack.pop(); - - currentFullyContained = fullyContainedStack.pop() || searchRect.contains(pointer.rect); - - if (currentFullyContained || pointer.rect.intersects(searchRect)) { - addChildrenToVisit(pointer, currentFullyContained); - currentIterator = pointer.objects != null ? pointer.objects.iterator() : null; - } else { - currentIterator = null; - } - } - } - - readUnlock(); - finished = true; - return false; - } - - @Override - public Edge next() { - if (next == null) { - throw new IllegalStateException("No next available!"); - } - - final Edge edge = next.data; - - next = null; - - return edge; - } - - } -} diff --git a/store/src/main/java/org/gephi/graph/impl/GraphStoreSpatialContextImpl.java b/store/src/main/java/org/gephi/graph/impl/GraphStoreSpatialContextImpl.java deleted file mode 100644 index 10ce47dd..00000000 --- a/store/src/main/java/org/gephi/graph/impl/GraphStoreSpatialContextImpl.java +++ /dev/null @@ -1,235 +0,0 @@ -package org.gephi.graph.impl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.function.Consumer; -import org.gephi.graph.api.Edge; -import org.gephi.graph.api.EdgeIterable; -import org.gephi.graph.api.Node; -import org.gephi.graph.api.NodeIterable; -import org.gephi.graph.api.Rect2D; -import org.gephi.graph.api.SpatialContext; - -/** - * Graph spatial indexing interface. - * TODO: unit tests!! - * TODO: measure performance loss due to having this. - * @author Eduardo Ramos - */ -public class GraphStoreSpatialContextImpl implements SpatialContext { - - private final GraphStore store; - private final NodesQuadTree nodesTree; - private final EdgesQuadTree edgesTree; - - public GraphStoreSpatialContextImpl(GraphStore store) { - this.store = store; - this.nodesTree = new NodesQuadTree(GraphStoreConfiguration.SPATIAL_INDEX_DIMENSION_BOUNDARY); - this.edgesTree = new EdgesQuadTree(GraphStoreConfiguration.SPATIAL_INDEX_DIMENSION_BOUNDARY); - } - - @Override - public NodeIterable getNodesInArea(Rect2D rect) { - return nodesTree.getNodes(rect); - } - - @Override - public void getNodesInArea(Rect2D rect, Consumer callback) { - nodesTree.getNodes(rect, callback); - } - - @Override - public EdgeIterable getEdgesInArea(Rect2D rect) { - return edgesTree.getEdges(rect); - } - - @Override - public void getEdgesInArea(Rect2D rect, Consumer callback) { - edgesTree.getEdges(rect, callback); - } - - protected void clearNodes() { - nodesTree.clear(); - } - - protected void addNode(final Node node) { - final float x = node.x(); - final float y = node.y(); - final float size = node.size(); - - final float minX = x - size; - final float minY = y - size; - final float maxX = x + size; - final float maxY = y + size; - - nodesTree.addNode(node, minX, minY, maxX, maxY); - } - - protected void removeNode(final Node node) { - nodesTree.removeNode(node); - } - - protected void moveNode(final Node node) { - final float x = node.x(); - final float y = node.y(); - final float size = node.size(); - - final float minX = x - size; - final float minY = y - size; - final float maxX = x + size; - final float maxY = y + size; - - nodesTree.updateNode(node, minX, minY, maxX, maxY); - - // Update node edges: - for (Edge edge : store.getEdges(node)) { - final Node opposite = edge.getSource() == node ? edge.getTarget() : edge.getSource(); - - final float x2 = opposite.x(); - final float y2 = opposite.y(); - - edgesTree.updateEdge(edge, min(x, x2), min(y, y2), max(x, x2), max(y, y2)); - } - } - - protected void addEdge(Edge edge) { - final Node source = edge.getSource(); - final Node target = edge.getTarget(); - - final float x1 = source.x(); - final float y1 = source.y(); - final float x2 = target.x(); - final float y2 = target.y(); - - final float minX = min(x1, x2); - final float minY = min(y1, y2); - final float maxX = max(x1, x2); - final float maxY = max(y1, y2); - - edgesTree.addEdge(edge, minX, minY, maxX, maxY); - } - - protected void removeEdge(Edge edge) { - edgesTree.removeEdge(edge); - } - - protected void clearEdges() { - edgesTree.clear(); - } - - private static float min(float a, float b) { - return (a <= b) ? a : b; - } - - private static float max(float a, float b) { - return (a >= b) ? a : b; - } - - protected EdgeIterableWrapper getEdgeIterableWrapper(Iterator edgeIterator) { - return new EdgeIterableWrapper(edgeIterator); - } - - protected NodeIterableWrapper getNodeIterableWrapper(Iterator nodeIterator) { - return new NodeIterableWrapper(nodeIterator); - } - - protected EdgeIterableWrapper getEdgeIterableWrapper(Iterator edgeIterator, boolean blocking) { - return new EdgeIterableWrapper(edgeIterator, blocking); - } - - protected NodeIterableWrapper getNodeIterableWrapper(Iterator nodeIterator, boolean blocking) { - return new NodeIterableWrapper(nodeIterator, blocking); - } - - protected class NodeIterableWrapper implements NodeIterable { - - protected final Iterator iterator; - protected final boolean blocking; - - public NodeIterableWrapper(Iterator iterator) { - this(iterator, true); - } - - public NodeIterableWrapper(Iterator iterator, boolean blocking) { - this.iterator = iterator; - this.blocking = blocking; - } - - @Override - public Iterator iterator() { - return iterator; - } - - @Override - public Node[] toArray() { - List list = new ArrayList<>(); - for (; iterator.hasNext();) { - list.add(iterator.next()); - } - return list.toArray(new Node[0]); - } - - @Override - public Collection toCollection() { - List list = new ArrayList<>(); - for (; iterator.hasNext();) { - list.add(iterator.next()); - } - return list; - } - - @Override - public void doBreak() { - if (blocking) { - nodesTree.readUnlock(); - } - } - } - - protected class EdgeIterableWrapper implements EdgeIterable { - - protected final Iterator iterator; - protected final boolean blocking; - - public EdgeIterableWrapper(Iterator iterator) { - this(iterator, true); - } - - public EdgeIterableWrapper(Iterator iterator, boolean blocking) { - this.iterator = iterator; - this.blocking = blocking; - } - - @Override - public Iterator iterator() { - return iterator; - } - - @Override - public Edge[] toArray() { - List list = new ArrayList<>(); - for (; iterator.hasNext();) { - list.add(iterator.next()); - } - return list.toArray(new Edge[0]); - } - - @Override - public Collection toCollection() { - List list = new ArrayList<>(); - for (; iterator.hasNext();) { - list.add(iterator.next()); - } - return list; - } - - @Override - public void doBreak() { - if (blocking) { - edgesTree.readUnlock(); - } - } - } -} diff --git a/store/src/main/java/org/gephi/graph/impl/IndexImpl.java b/store/src/main/java/org/gephi/graph/impl/IndexImpl.java deleted file mode 100644 index 1f948abb..00000000 --- a/store/src/main/java/org/gephi/graph/impl/IndexImpl.java +++ /dev/null @@ -1,1048 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.impl; - -import it.unimi.dsi.fastutil.booleans.BooleanArrays; -import it.unimi.dsi.fastutil.bytes.Byte2ObjectAVLTreeMap; -import it.unimi.dsi.fastutil.bytes.ByteArrays; -import it.unimi.dsi.fastutil.chars.Char2ObjectAVLTreeMap; -import it.unimi.dsi.fastutil.chars.CharArrays; -import it.unimi.dsi.fastutil.doubles.Double2ObjectAVLTreeMap; -import it.unimi.dsi.fastutil.doubles.DoubleArrays; -import it.unimi.dsi.fastutil.floats.Float2ObjectAVLTreeMap; -import it.unimi.dsi.fastutil.floats.FloatArrays; -import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap; -import it.unimi.dsi.fastutil.ints.IntArrays; -import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap; -import it.unimi.dsi.fastutil.longs.LongArrays; -import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrays; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import it.unimi.dsi.fastutil.shorts.Short2ObjectAVLTreeMap; -import it.unimi.dsi.fastutil.shorts.ShortArrays; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.SortedMap; -import org.gephi.graph.api.Column; -import org.gephi.graph.api.Index; -import org.gephi.graph.api.Element; - -public class IndexImpl implements Index { - - protected final TableLock lock; - protected final ColumnStore columnStore; - protected AbstractIndex[] columns; - protected int columnsCount; - - public IndexImpl(ColumnStore columnStore) { - this.columnStore = columnStore; - this.columns = new AbstractIndex[0]; - this.lock = columnStore.lock; - } - - @Override - public Class getIndexClass() { - return columnStore.elementType; - } - - @Override - public String getIndexName() { - return "index_" + columnStore.elementType.getCanonicalName(); - } - - @Override - public int count(Column column, Object value) { - checkNonNullColumnObject(column); - - lock(); - try { - AbstractIndex index = getIndex((ColumnImpl) column); - return index.getCount(value); - } finally { - unlock(); - } - } - - public int count(String key, Object value) { - checkNonNullObject(key); - - AbstractIndex index = getIndex(key); - return index.getCount(value); - } - - public Iterable get(String key, Object value) { - checkNonNullObject(key); - - AbstractIndex index = getIndex(key); - return index.getValueSet(value); - } - - @Override - public Iterable get(Column column, Object value) { - checkNonNullColumnObject(column); - - if (lock != null) { - lock.lock(); - AbstractIndex index = getIndex((ColumnImpl) column); - Set valueSet = index.getValueSet(value); - return valueSet == null ? null : new LockableIterable<>(index.getValueSet(value)); - } - AbstractIndex index = getIndex((ColumnImpl) column); - return index.getValueSet(value); - } - - @Override - public boolean isSortable(Column column) { - checkNonNullColumnObject(column); - - lock(); - try { - AbstractIndex index = getIndex((ColumnImpl) column); - - return index.isSortable(); - } finally { - unlock(); - } - } - - @Override - public Number getMinValue(Column column) { - checkNonNullColumnObject(column); - - lock(); - try { - AbstractIndex index = getIndex((ColumnImpl) column); - return index.getMinValue(); - } finally { - unlock(); - } - } - - @Override - public Number getMaxValue(Column column) { - checkNonNullColumnObject(column); - lock(); - try { - AbstractIndex index = getIndex((ColumnImpl) column); - return index.getMaxValue(); - } finally { - unlock(); - } - } - - public Iterable>> get(Column column) { - checkNonNullColumnObject(column); - - AbstractIndex index = getIndex((ColumnImpl) column); - return index; - } - - @Override - public Collection values(Column column) { - checkNonNullColumnObject(column); - - lock(); - try { - AbstractIndex index = getIndex((ColumnImpl) column); - return new ArrayList(index.values()); - } finally { - unlock(); - } - } - - @Override - public int countValues(Column column) { - checkNonNullColumnObject(column); - lock(); - try { - AbstractIndex index = getIndex((ColumnImpl) column); - return index.countValues(); - } finally { - unlock(); - } - } - - @Override - public int countElements(Column column) { - checkNonNullColumnObject(column); - lock(); - try { - AbstractIndex index = getIndex((ColumnImpl) column); - return index.elements; - } finally { - unlock(); - } - } - - public Object put(String key, Object value, T element) { - checkNonNullObject(key); - - AbstractIndex index = getIndex(key); - return index.putValue(element, value); - } - - public Object put(Column column, Object value, T element) { - checkNonNullColumnObject(column); - - AbstractIndex index = getIndex((ColumnImpl) column); - return index.putValue(element, value); - } - - public void remove(String key, Object value, T element) { - checkNonNullObject(key); - - AbstractIndex index = getIndex(key); - index.removeValue(element, value); - } - - public void remove(Column column, Object value, T element) { - checkNonNullColumnObject(column); - - AbstractIndex index = getIndex((ColumnImpl) column); - index.removeValue(element, value); - } - - public Object set(String key, Object oldValue, Object value, T element) { - checkNonNullObject(key); - - AbstractIndex index = getIndex(key); - return index.replaceValue(element, oldValue, value); - } - - public Object set(Column column, Object oldValue, Object value, T element) { - checkNonNullColumnObject(column); - - AbstractIndex index = getIndex((ColumnImpl) column); - return index.replaceValue(element, oldValue, value); - } - - public void clear() { - for (AbstractIndex ai : columns) { - if (ai != null) { - ai.clear(); - } - } - } - - protected void addColumn(ColumnImpl col) { - if (col.isIndexed()) { - ensureColumnSize(col.storeId); - AbstractIndex index = createIndex(col); - columns[col.storeId] = index; - columnsCount++; - } - } - - protected void addAllColumns(ColumnImpl[] cols) { - ensureColumnSize(cols.length); - for (ColumnImpl col : cols) { - if (col.isIndexed()) { - AbstractIndex index = createIndex(col); - columns[col.storeId] = index; - columnsCount++; - } - } - } - - protected void removeColumn(ColumnImpl col) { - if (col.isIndexed()) { - AbstractIndex index = columns[col.storeId]; - index.destroy(); - columns[col.storeId] = null; - columnsCount--; - } - } - - protected boolean hasColumn(ColumnImpl col) { - if (col.isIndexed()) { - int id = col.storeId; - if (id != ColumnStore.NULL_ID && columns.length > id && columns[id].column == col) { - return true; - } - } - return false; - } - - protected AbstractIndex getIndex(ColumnImpl col) { - if (col.isIndexed()) { - int id = col.storeId; - if (id != ColumnStore.NULL_ID && columns.length > id) { - AbstractIndex index = columns[id]; - if (index != null && index.column == col) { - return index; - } - } - } - return null; - } - - protected AbstractIndex getIndex(String key) { - int id = columnStore.getColumnIndex(key); - if (id != ColumnStore.NULL_ID && columns.length > id) { - return columns[id]; - } - return null; - } - - protected void destroy() { - for (AbstractIndex ai : columns) { - if (ai != null) { - ai.destroy(); - } - } - columns = new AbstractIndex[0]; - columnsCount = 0; - } - - protected int size() { - return columnsCount; - } - - AbstractIndex createIndex(ColumnImpl column) { - if (column.getTypeClass().equals(Byte.class)) { - // Byte - return new ByteIndex(column); - } else if (column.getTypeClass().equals(Short.class)) { - // Short - return new ShortIndex(column); - } else if (column.getTypeClass().equals(Integer.class)) { - // Integer - return new IntegerIndex(column); - } else if (column.getTypeClass().equals(Long.class)) { - // Long - return new LongIndex(column); - } else if (column.getTypeClass().equals(Float.class)) { - // Float - return new FloatIndex(column); - } else if (column.getTypeClass().equals(Double.class)) { - // Double - return new DoubleIndex(column); - } else if (Number.class.isAssignableFrom(column.getTypeClass())) { - // Other numbers - return new GenericNumberIndex(column); - } else if (column.getTypeClass().equals(Boolean.class)) { - // Boolean - return new BooleanIndex(column); - } else if (column.getTypeClass().equals(Character.class)) { - // Char - return new CharIndex(column); - } else if (column.getTypeClass().equals(String.class)) { - // String - return new DefaultIndex(column); - } else if (column.getTypeClass().equals(byte[].class)) { - // Byte Array - return new ByteArrayIndex(column); - } else if (column.getTypeClass().equals(short[].class)) { - // Short Array - return new ShortArrayIndex(column); - } else if (column.getTypeClass().equals(int[].class)) { - // Integer Array - return new IntegerArrayIndex(column); - } else if (column.getTypeClass().equals(long[].class)) { - // Long Array - return new LongArrayIndex(column); - } else if (column.getTypeClass().equals(float[].class)) { - // Float array - return new FloatArrayIndex(column); - } else if (column.getTypeClass().equals(double[].class)) { - // Double array - return new DoubleArrayIndex(column); - } else if (column.getTypeClass().equals(boolean[].class)) { - // Boolean array - return new BooleanArrayIndex(column); - } else if (column.getTypeClass().equals(char[].class)) { - // Char array - return new CharArrayIndex(column); - } else if (column.getTypeClass().equals(String[].class)) { - // String array - return new DefaultArrayIndex(column); - } else if (column.getTypeClass().isArray()) { - // Default Array - return new DefaultArrayIndex(column); - } - return new DefaultIndex(column); - } - - private void ensureColumnSize(int index) { - if (index >= columns.length) { - AbstractIndex[] newArray = new AbstractIndex[index + 1]; - System.arraycopy(columns, 0, newArray, 0, columns.length); - columns = newArray; - } - } - - void lock() { - if (lock != null) { - lock.lock(); - } - } - - void unlock() { - if (lock != null) { - lock.unlock(); - } - } - - void checkNonNullObject(final Object o) { - if (o == null) { - throw new NullPointerException(); - } - } - - void checkNonNullColumnObject(final Object o) { - if (o == null) { - throw new NullPointerException(); - } - if (!(o instanceof ColumnImpl)) { - throw new ClassCastException("Must be ColumnImpl object"); - } - } - - protected abstract class AbstractIndex implements Iterable>> { - - // Const - public static final boolean TRIMMING_ENABLED = false; - public static final int TRIMMING_FREQUENCY = 30; - // Data - protected final ColumnImpl column; - protected final Set nullSet; - protected Map> map; - // Variable - protected int elements; - - public AbstractIndex(ColumnImpl column) { - this.column = column; - this.nullSet = new ObjectOpenHashSet<>(); - } - - public Object putValue(T element, Object value) { - if (value == null) { - if (nullSet.add(element)) { - elements++; - } - } else { - Set set = getValueSet((K) value); - if (set == null) { - set = addValue((K) value); - } - value = ((ValueSet) set).value; - - if (set.add(element)) { - elements++; - } - } - return value; - } - - public void removeValue(T element, Object value) { - if (value == null) { - if (nullSet.remove(element)) { - elements--; - } - } else { - Set set = getValueSet((K) value); - if (set.remove(element)) { - elements--; - } - if (set.isEmpty()) { - removeValue((K) value); - } - } - } - - public Object replaceValue(T element, K oldValue, K newValue) { - removeValue(element, oldValue); - return putValue(element, newValue); - } - - public int getCount(K value) { - if (value == null) { - return nullSet.size(); - } - Set valueSet = getValueSet(value); - if (valueSet != null) { - return valueSet.size(); - } else { - return 0; - } - } - - public Collection values() { - return new WithNullDecorator(); - } - - public int countValues() { - return (nullSet.isEmpty() ? 0 : 1) + map.size(); - } - - public Number getMinValue() { - if (isSortable()) { - if (map.isEmpty()) { - return null; - } else { - return (Number) ((SortedMap) map).firstKey(); - } - } else { - throw new UnsupportedOperationException("'" + column.getId() + "' is not a sortable column (" + column - .getTypeClass().getSimpleName() + ")."); - } - } - - public Number getMaxValue() { - if (isSortable()) { - if (map.isEmpty()) { - return null; - } else { - return (Number) ((SortedMap) map).lastKey(); - } - } else { - throw new UnsupportedOperationException("'" + column.getId() + "' is not a sortable column (" + column - .getTypeClass().getSimpleName() + ")."); - } - } - - protected void destroy() { - map = null; - nullSet.clear(); - elements = 0; - } - - protected void clear() { - map.clear(); - nullSet.clear(); - elements = 0; - } - - @Override - public Iterator>> iterator() { - return new EntryIterator(); - } - - protected Set getValueSet(K value) { - if (value == null) { - return nullSet; - } - return map.get(value); - } - - protected void removeValue(K value) { - map.remove(value); - } - - protected Set addValue(K value) { - ValueSet valueSet = new ValueSet(value); - map.put(value, valueSet); - return valueSet; - } - - protected boolean isSortable() { - return Number.class.isAssignableFrom(column.getTypeClass()) && map instanceof SortedMap; - } - - protected final class WithNullDecorator implements Collection { - - private boolean hasNull() { - return !nullSet.isEmpty(); - } - - @Override - public int size() { - return (hasNull() ? 1 : 0) + map.size(); - } - - @Override - public boolean isEmpty() { - return !hasNull() && map.isEmpty(); - } - - @Override - public boolean contains(Object o) { - if (o == null && hasNull()) { - return true; - } else if (o != null) { - return map.containsKey((K) o); - } - return false; - } - - @Override - public Iterator iterator() { - return new WithNullIterator(); - } - - @Override - public Object[] toArray() { - if (hasNull()) { - Object[] res = new Object[map.size() + 1]; - res[0] = null; - System.arraycopy(map.keySet().toArray(), 0, res, 1, map.size()); - return res; - } else { - return map.keySet().toArray(); - } - } - - @Override - public Object[] toArray(Object[] array) { - - if (hasNull()) { - if (array.length < size()) { - array = (K[]) java.lang.reflect.Array.newInstance(array.getClass().getComponentType(), map - .size() + 1); - } - array[0] = null; - System.arraycopy(map.keySet().toArray(), 0, array, 1, map.size()); - return array; - } else { - return map.keySet().toArray(array); - } - } - - @Override - public boolean add(Object e) { - throw new UnsupportedOperationException("Not supported"); - } - - @Override - public boolean remove(Object o) { - throw new UnsupportedOperationException("Not supported"); - } - - @Override - public boolean containsAll(Collection clctn) { - for (Object o : clctn) { - if (o == null && nullSet.isEmpty()) { - return false; - } else if (o != null && !map.containsKey((K) o)) { - return false; - } - } - return true; - } - - @Override - public boolean addAll(Collection clctn) { - throw new UnsupportedOperationException("Not supported"); - } - - @Override - public boolean removeAll(Collection clctn) { - throw new UnsupportedOperationException("Not supported"); - } - - @Override - public boolean retainAll(Collection clctn) { - throw new UnsupportedOperationException("Not supported"); - } - - @Override - public void clear() { - throw new UnsupportedOperationException("Not supported"); - } - - private final class WithNullIterator implements Iterator { - - private final Iterator mapIterator; - private boolean hasNull; - - public WithNullIterator() { - hasNull = hasNull(); - mapIterator = map.keySet().iterator(); - } - - @Override - public boolean hasNext() { - if (hasNull) { - return true; - } - return mapIterator.hasNext(); - } - - @Override - public K next() { - if (hasNull) { - hasNull = false; - return null; - } - return mapIterator.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Not supported operation."); - } - } - } - - private final class EntryIterator implements Iterator>> { - - private final Iterator>> mapIterator; - private NullEntry nullEntry; - - public EntryIterator() { - if (!nullSet.isEmpty()) { - nullEntry = new NullEntry(); - } - mapIterator = map.entrySet().iterator(); - } - - @Override - public boolean hasNext() { - if (nullEntry != null) { - return true; - } - return mapIterator.hasNext(); - } - - @Override - public Map.Entry> next() { - if (nullEntry != null) { - NullEntry ne = nullEntry; - nullEntry = null; - return ne; - } - return mapIterator.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Not supported operation."); - } - } - - private class NullEntry implements Map.Entry> { - - @Override - public K getKey() { - return null; - } - - @Override - public Set getValue() { - return nullSet; - } - - @Override - public Set setValue(Set v) { - throw new UnsupportedOperationException("Not supported operation."); - } - } - } - - private static final class ValueSet implements Set { - - private final K value; - private final Set set; - - public ValueSet(K value) { - this.value = value; - this.set = new ObjectOpenHashSet<>(); - } - - @Override - public int size() { - return set.size(); - } - - @Override - public boolean isEmpty() { - return set.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return set.contains(o); - } - - @Override - public Iterator iterator() { - return set.iterator(); - } - - @Override - public Object[] toArray() { - return set.toArray(); - } - - @Override - public T[] toArray(T[] ts) { - return set.toArray(ts); - } - - @Override - public boolean add(T e) { - return set.add(e); - } - - @Override - public boolean remove(Object o) { - return set.remove(o); - } - - @Override - public boolean containsAll(Collection clctn) { - return set.containsAll(clctn); - } - - @Override - public boolean addAll(Collection clctn) { - throw new UnsupportedOperationException("Not supported operation."); - } - - @Override - public boolean retainAll(Collection clctn) { - throw new UnsupportedOperationException("Not supported operation."); - } - - @Override - public boolean removeAll(Collection clctn) { - throw new UnsupportedOperationException("Not supported operation."); - } - - @Override - public void clear() { - throw new UnsupportedOperationException("Not supported operation."); - } - - @Override - public boolean equals(Object o) { - return set.equals(o); - } - - @Override - public int hashCode() { - return set.hashCode(); - } - } - - protected class DefaultIndex extends AbstractIndex { - - public DefaultIndex(ColumnImpl column) { - super(column); - - map = new Object2ObjectOpenHashMap<>(); - } - } - - protected class BooleanIndex extends AbstractIndex { - - public BooleanIndex(ColumnImpl column) { - super(column); - - map = new Object2ObjectOpenHashMap<>(); - } - } - - protected class DoubleIndex extends AbstractIndex { - - public DoubleIndex(ColumnImpl column) { - super(column); - - map = new Double2ObjectAVLTreeMap<>(); - } - } - - protected class IntegerIndex extends AbstractIndex { - - public IntegerIndex(ColumnImpl column) { - super(column); - - map = new Int2ObjectAVLTreeMap<>(); - } - } - - protected class FloatIndex extends AbstractIndex { - - public FloatIndex(ColumnImpl column) { - super(column); - - map = new Float2ObjectAVLTreeMap<>(); - } - } - - protected class LongIndex extends AbstractIndex { - - public LongIndex(ColumnImpl column) { - super(column); - - map = new Long2ObjectAVLTreeMap<>(); - } - } - - protected class ShortIndex extends AbstractIndex { - - public ShortIndex(ColumnImpl column) { - super(column); - - map = new Short2ObjectAVLTreeMap<>(); - } - } - - protected class ByteIndex extends AbstractIndex { - - public ByteIndex(ColumnImpl column) { - super(column); - - map = new Byte2ObjectAVLTreeMap<>(); - } - } - - protected class GenericNumberIndex extends AbstractIndex { - - public GenericNumberIndex(ColumnImpl column) { - super(column); - - map = new Object2ObjectAVLTreeMap<>(); - } - } - - protected class CharIndex extends AbstractIndex { - - public CharIndex(ColumnImpl column) { - super(column); - - map = new Char2ObjectAVLTreeMap<>(); - } - } - - protected class DefaultArrayIndex extends AbstractIndex { - - public DefaultArrayIndex(ColumnImpl column) { - super(column); - - map = new Object2ObjectOpenCustomHashMap<>(ObjectArrays.HASH_STRATEGY); - } - } - - protected class BooleanArrayIndex extends AbstractIndex { - - public BooleanArrayIndex(ColumnImpl column) { - super(column); - - map = new Object2ObjectOpenCustomHashMap<>(BooleanArrays.HASH_STRATEGY); - } - } - - protected class DoubleArrayIndex extends AbstractIndex { - - public DoubleArrayIndex(ColumnImpl column) { - super(column); - - map = new Object2ObjectOpenCustomHashMap<>(DoubleArrays.HASH_STRATEGY); - } - } - - protected class IntegerArrayIndex extends AbstractIndex { - - public IntegerArrayIndex(ColumnImpl column) { - super(column); - - map = new Object2ObjectOpenCustomHashMap<>(IntArrays.HASH_STRATEGY); - } - } - - protected class FloatArrayIndex extends AbstractIndex { - - public FloatArrayIndex(ColumnImpl column) { - super(column); - - map = new Object2ObjectOpenCustomHashMap<>(FloatArrays.HASH_STRATEGY); - } - } - - protected class LongArrayIndex extends AbstractIndex { - - public LongArrayIndex(ColumnImpl column) { - super(column); - - map = new Object2ObjectOpenCustomHashMap<>(LongArrays.HASH_STRATEGY); - } - } - - protected class ShortArrayIndex extends AbstractIndex { - - public ShortArrayIndex(ColumnImpl column) { - super(column); - - map = new Object2ObjectOpenCustomHashMap<>(ShortArrays.HASH_STRATEGY); - } - } - - protected class ByteArrayIndex extends AbstractIndex { - - public ByteArrayIndex(ColumnImpl column) { - super(column); - - map = new Object2ObjectOpenCustomHashMap<>(ByteArrays.HASH_STRATEGY); - } - } - - protected class CharArrayIndex extends AbstractIndex { - - public CharArrayIndex(ColumnImpl column) { - super(column); - - map = new Object2ObjectOpenCustomHashMap<>(CharArrays.HASH_STRATEGY); - } - } - - private class LockableIterable implements Iterable { - - private final Iterable ite; - - public LockableIterable(Iterable ite) { - this.ite = ite; - } - - @Override - public Iterator iterator() { - return new LockableIterator<>(ite.iterator()); - } - } - - private class LockableIterator implements Iterator { - - private final Iterator itr; - - public LockableIterator(Iterator itr) { - this.itr = itr; - } - - @Override - public boolean hasNext() { - boolean n = itr.hasNext(); - if (!n) { - lock.unlock(); - } - return n; - } - - @Override - public T next() { - return itr.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Not supported."); - } - } -} diff --git a/store/src/main/java/org/gephi/graph/impl/IntervalIndexImpl.java b/store/src/main/java/org/gephi/graph/impl/IntervalIndexImpl.java deleted file mode 100644 index 7a3952a9..00000000 --- a/store/src/main/java/org/gephi/graph/impl/IntervalIndexImpl.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.impl; - -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import it.unimi.dsi.fastutil.objects.ObjectSet; -import java.util.Map; -import org.gephi.graph.api.Element; -import org.gephi.graph.api.ElementIterable; -import org.gephi.graph.api.Interval; -import org.gephi.graph.api.types.IntervalMap; -import org.gephi.graph.api.types.IntervalSet; - -public class IntervalIndexImpl extends TimeIndexImpl> { - - public IntervalIndexImpl(TimeIndexStore> store, boolean main) { - super(store, main); - } - - @Override - public double getMinTimestamp() { - if (mainIndex) { - Interval2IntTreeMap sortedMap = (Interval2IntTreeMap) timestampIndexStore.timeSortedMap; - if (!sortedMap.isEmpty()) { - return sortedMap.getLow(); - } - } else { - Interval2IntTreeMap sortedMap = (Interval2IntTreeMap) timestampIndexStore.timeSortedMap; - if (!sortedMap.isEmpty()) { - for (Map.Entry entry : sortedMap.entrySet()) { - int index = entry.getValue(); - if (index < timestamps.length) { - TimeIndexEntry intervalEntry = timestamps[index]; - if (intervalEntry != null) { - return entry.getKey().getLow(); - } - } - } - } - } - return Double.NEGATIVE_INFINITY; - } - - @Override - public double getMaxTimestamp() { - if (mainIndex) { - Interval2IntTreeMap sortedMap = (Interval2IntTreeMap) timestampIndexStore.timeSortedMap; - if (!sortedMap.isEmpty()) { - return sortedMap.getHigh(); - } - } else { - // TODO Better algorithm to find max - Interval2IntTreeMap sortedMap = (Interval2IntTreeMap) timestampIndexStore.timeSortedMap; - if (!sortedMap.isEmpty()) { - double max = Double.NEGATIVE_INFINITY; - boolean found = false; - for (Map.Entry entry : sortedMap.entrySet()) { - int index = entry.getValue(); - if (index < timestamps.length) { - TimeIndexEntry intervalEntry = timestamps[index]; - if (intervalEntry != null) { - found = true; - max = Math.max(max, entry.getKey().getHigh()); - } - } - } - if (found) { - return max; - } - } - - } - return Double.POSITIVE_INFINITY; - } - - @Override - public ElementIterable get(double timestamp) { - checkDouble(timestamp); - - readLock(); - ObjectSet elements = new ObjectOpenHashSet<>(); - Interval2IntTreeMap sortedMap = (Interval2IntTreeMap) timestampIndexStore.timeSortedMap; - if (!sortedMap.isEmpty()) { - for (Integer index : sortedMap.values(timestamp)) { - if (index < timestamps.length) { - TimeIndexEntry ts = timestamps[index]; - if (ts != null) { - elements.addAll(ts.elementSet); - } - } - } - } - if (!elements.isEmpty()) { - return new ElementIterableImpl(new ElementIteratorImpl(elements.iterator())); - } - readUnlock(); - return ElementIterable.EMPTY; - } - - @Override - public ElementIterable get(Interval interval) { - - readLock(); - ObjectSet elements = new ObjectOpenHashSet<>(); - Interval2IntTreeMap sortedMap = (Interval2IntTreeMap) timestampIndexStore.timeSortedMap; - if (!sortedMap.isEmpty()) { - for (Integer index : sortedMap.values(interval)) { - if (index < timestamps.length) { - TimeIndexEntry ts = timestamps[index]; - if (ts != null) { - elements.addAll(ts.elementSet); - } - } - } - } - if (!elements.isEmpty()) { - return new ElementIterableImpl(new ElementIteratorImpl(elements.iterator())); - } - readUnlock(); - return ElementIterable.EMPTY; - } -} diff --git a/store/src/main/java/org/gephi/graph/impl/NodesQuadTree.java b/store/src/main/java/org/gephi/graph/impl/NodesQuadTree.java deleted file mode 100644 index cb96bba5..00000000 --- a/store/src/main/java/org/gephi/graph/impl/NodesQuadTree.java +++ /dev/null @@ -1,687 +0,0 @@ -package org.gephi.graph.impl; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; -import org.gephi.graph.api.Node; -import org.gephi.graph.api.NodeIterable; -import org.gephi.graph.api.Rect2D; - -/** - * Adapted from https://bitbucket.org/C3/quadtree/wiki/Home - * - * TODO: unit tests!! - * @author Eduardo Ramos - */ -public class NodesQuadTree { - - private static final int MAX_LEVELS = 16; - private static final int MAX_OBJECTS_PER_NODE = 2; - - private final GraphLock lock = new GraphLock(); - private Map wrappedDictionary = new LinkedHashMap<>(); - - private QuadTreeNode quadTreeRoot; - - public NodesQuadTree(Rect2D rect) { - quadTreeRoot = new QuadTreeNode(rect); - } - - public NodesQuadTree(float dimensionMax) { - this(-dimensionMax, -dimensionMax, dimensionMax, dimensionMax); - } - - public NodesQuadTree(float minX, float minY, float maxX, float maxY) { - quadTreeRoot = new QuadTreeNode(new Rect2D(minX, minY, maxX, maxY)); - } - - public Rect2D quadRect() { - return quadTreeRoot.quadRect(); - } - - public NodeIterable getNodes(Rect2D searchRect) { - return quadTreeRoot.getNodes(searchRect); - } - - public void getNodes(Rect2D searchRect, Consumer callback) { - quadTreeRoot.getNodes(searchRect, callback); - } - - public NodeIterable getNodes(float minX, float minY, float maxX, float maxY) { - return quadTreeRoot.getNodes(new Rect2D(minX, minY, maxX, maxY)); - } - - public void getNodes(float minX, float minY, float maxX, float maxY, Consumer callback) { - quadTreeRoot.getNodes(new Rect2D(minX, minY, maxX, maxY), callback); - } - - public NodeIterable getAllNodes() { - return quadTreeRoot.getAllNodes(); - } - - public void getAllNodes(Consumer callback) { - quadTreeRoot.getAllNodes(callback); - } - - public boolean updateNode(Node item, float minX, float minY, float maxX, float maxY) { - writeLock(); - try { - final QuadTreeObject obj = wrappedDictionary.get(item); - if (obj != null) { - obj.updateItemCoords(minX, minY, maxX, maxY); - quadTreeRoot.update(obj); - return true; - } else { - return false; - } - } finally { - writeUnlock(); - } - } - - public boolean addNode(Node item) { - final float x = item.x(); - final float y = item.y(); - final float size = item.size(); - - final float minX = x - size; - final float minY = y - size; - final float maxX = x + size; - final float maxY = y + size; - - return addNode(item, minX, minY, maxX, maxY); - } - - public boolean addNode(Node item, float minX, float minY, float maxX, float maxY) { - writeLock(); - try { - if (!containsNode(item)) { - final QuadTreeObject wrappedObject = new QuadTreeObject(item, minX, minY, maxX, maxY); - wrappedDictionary.put(item, wrappedObject); - quadTreeRoot.insert(wrappedObject); - return true; - } else { - return false; - } - } finally { - writeUnlock(); - } - } - - public void clear() { - writeLock(); - try { - wrappedDictionary.clear(); - quadTreeRoot.clear(); - } finally { - writeUnlock(); - } - } - - public boolean containsNode(Node item) { - readLock(); - try { - return wrappedDictionary.containsKey(item); - } finally { - readUnlock(); - } - } - - public int count() { - readLock(); - try { - return wrappedDictionary.size(); - } finally { - readUnlock(); - } - } - - public boolean removeNode(Node item) { - writeLock(); - try { - final QuadTreeObject obj = wrappedDictionary.get(item); - if (obj != null) { - quadTreeRoot.delete(obj, true); - wrappedDictionary.remove(item); - return true; - } else { - return false; - } - } finally { - writeUnlock(); - } - } - - public void readLock() { - if (lock != null) { - lock.readLock(); - } - } - - public void readUnlock() { - if (lock != null) { - lock.readUnlock(); - } - } - - public void writeLock() { - if (lock != null) { - lock.writeLock(); - } - } - - public void writeUnlock() { - if (lock != null) { - lock.writeUnlock(); - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - - quadTreeRoot.toString(sb); - - return sb.toString(); - } - - private class QuadTreeObject { - - private final Node data; - private float minX, minY, maxX, maxY; - - private QuadTreeNode owner; - - public QuadTreeObject(Node data, float minX, float minY, float maxX, float maxY) { - this.data = data; - updateItemCoords(minX, minY, maxX, maxY); - } - - private void updateItemCoords(float minX, float minY, float maxX, float maxY) { - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - } - - private class QuadTreeNode { - - private Set objects = null; - private final Rect2D rect; // The area this QuadTree represents - - private final QuadTreeNode parent; // The parent of this quad - private final int level; - - private QuadTreeNode childTL = null; // Top Left Child - private QuadTreeNode childTR = null; // Top Right Child - private QuadTreeNode childBL = null; // Bottom Left Child - private QuadTreeNode childBR = null; // Bottom Right Child - - public Rect2D quadRect() { - return rect; - } - - public QuadTreeNode topLeftChild() { - return childTL; - } - - public QuadTreeNode topRightChild() { - return childTR; - } - - public QuadTreeNode bottomLeftChild() { - return childBL; - } - - public QuadTreeNode bottomRightChild() { - return childBR; - } - - public QuadTreeNode parent() { - return parent; - } - - public int count() { - return objectCount(); - } - - public boolean isEmptyLeaf() { - return count() == 0 && childTL == null; - } - - public QuadTreeNode(Rect2D rect) { - this(null, 0, rect); - } - - private QuadTreeNode(QuadTreeNode parent, int level, Rect2D rect) { - this.level = level; - this.rect = rect; - this.parent = parent; - } - - private void add(QuadTreeObject item) { - if (objects == null) { - objects = new LinkedHashSet<>(); - } - - item.owner = this; - objects.add(item); - } - - private void remove(QuadTreeObject item) { - if (objects != null) { - objects.remove(item); - } - } - - private int objectCount() { - int count = 0; - - // add the objects at this level - if (objects != null) { - count += objects.size(); - } - - // add the objects that are contained in the children - if (childTL != null) { - count += childTL.objectCount(); - count += childTR.objectCount(); - count += childBL.objectCount(); - count += childBR.objectCount(); - } - - return count; - } - - private void subdivide() { - // We've reached capacity, subdivide... - final float minX = rect.minX; - final float halfX = (rect.minX + rect.maxX) / 2; - final float maxX = rect.maxX; - - final float minY = rect.minY; - final float halfY = (rect.minY + rect.maxY) / 2; - final float maxY = rect.maxY; - - childTL = new QuadTreeNode(this, level + 1, new Rect2D(minX, minY, halfX, halfY)); - childTR = new QuadTreeNode(this, level + 1, new Rect2D(halfX, minY, maxX, halfY)); - childBL = new QuadTreeNode(this, level + 1, new Rect2D(minX, halfY, halfX, maxY)); - childBR = new QuadTreeNode(this, level + 1, new Rect2D(halfX, halfY, maxX, maxY)); - - // If they're completely contained by the quad, bump objects down - final Iterator iterator = objects.iterator(); - while (iterator.hasNext()) { - QuadTreeObject obj = iterator.next(); - QuadTreeNode destTree = getDestinationTree(obj); - if (destTree != this) { - // Insert to the appropriate tree, remove the object, and - // back up one in the loop - destTree.insert(obj); - - iterator.remove(); - } - } - } - - private QuadTreeNode getDestinationTree(QuadTreeObject item) { - // If a child can't contain an object, it will live in this Quad - final QuadTreeNode destTree; - - final float minX = item.minX; - final float minY = item.minY; - final float maxX = item.maxX; - final float maxY = item.maxY; - - if (childTL.quadRect().contains(minX, minY, maxX, maxY)) { - destTree = childTL; - } else if (childTR.quadRect().contains(minX, minY, maxX, maxY)) { - destTree = childTR; - } else if (childBL.quadRect().contains(minX, minY, maxX, maxY)) { - destTree = childBL; - } else if (childBR.quadRect().contains(minX, minY, maxX, maxY)) { - destTree = childBR; - } else { - destTree = this; - } - - return destTree; - } - - private void relocate(QuadTreeObject item) { - // Are we still inside our parent? - if (quadRect().contains(item.minX, item.minY, item.maxX, item.maxY)) { - // Good, have we moved inside any of our children? - if (childTL != null) { - QuadTreeNode dest = getDestinationTree(item); - if (item.owner != dest) { - // Delete the item from this quad and add it to our - // child - // Note: Do NOT clean during this call, it can - // potentially delete our destination quad - QuadTreeNode formerOwner = item.owner; - delete(item, false); - dest.insert(item); - - // Clean up ourselves - formerOwner.cleanUpwards(); - } - } - } else { - // We don't fit here anymore, move up, if we can - if (parent != null) { - parent.relocate(item); - } - } - } - - private void cleanUpwards() { - if (childTL != null) { - // If all the children are empty leaves, delete all the children - if (childTL.isEmptyLeaf() && childTR.isEmptyLeaf() && childBL.isEmptyLeaf() && childBR.isEmptyLeaf()) { - childTL = null; - childTR = null; - childBL = null; - childBR = null; - - if (parent != null && count() == 0) { - parent.cleanUpwards(); - } - } - } else { - // I could be one of 4 empty leaves, tell my parent to clean up - if (parent != null && count() == 0) { - parent.cleanUpwards(); - } - } - } - - private void clear() { - // clear out the children, if we have any - if (childTL != null) { - childTL.clear(); - childTR.clear(); - childBL.clear(); - childBR.clear(); - } - - // clear any objects at this level - if (objects != null) { - objects.clear(); - objects = null; - } - - // Set the children to null - childTL = null; - childTR = null; - childBL = null; - childBR = null; - } - - private void delete(QuadTreeObject item, boolean clean) { - if (item.owner != null) { - if (item.owner == this) { - remove(item); - if (clean) { - cleanUpwards(); - } - } else { - item.owner.delete(item, clean); - } - } - } - - private void insert(QuadTreeObject item) { - // If this quad doesn't contain the items rectangle, do nothing, - // unless we are the root - if (!rect.contains(item.minX, item.minY, item.maxX, item.maxY)) { - if (parent == null) { - // This object is outside of the QuadTreeXNA bounds, we - // should add it at the root level - add(item); - } else { - throw new IllegalStateException( - "We are not the root, and this object doesn't fit here. How did we get here?"); - } - } - - if (objects == null || (childTL == null && (level >= MAX_LEVELS || objects.size() + 1 <= MAX_OBJECTS_PER_NODE))) { - // If there's room to add the object, just add it - add(item); - } else { - // No quads, create them and bump objects down where appropriate - if (childTL == null) { - subdivide(); - } - - // Find out which tree this object should go in and add it there - final QuadTreeNode destTree = getDestinationTree(item); - if (destTree == this) { - add(item); - } else { - destTree.insert(item); - } - } - } - - private NodeIterable getNodes(Rect2D searchRect) { - return new QuadTreeNodesIterable(searchRect); - } - - private NodeIterable getAllNodes() { - return new QuadTreeNodesIterable(null); - } - - private void getNodes(Rect2D searchRect, Consumer callback) { - if (searchRect.contains(this.rect)) { - this.getAllNodes(callback); - } else if (searchRect.intersects(this.rect)) { - if (objects != null && !objects.isEmpty()) { - for (QuadTreeObject obj : objects) { - if (searchRect.intersects(obj.minX, obj.minY, obj.maxX, obj.maxY)) { - callback.accept(obj.data); - } - } - } - - if (childTL != null) { - childTL.getNodes(searchRect, callback); - childTR.getNodes(searchRect, callback); - childBL.getNodes(searchRect, callback); - childBR.getNodes(searchRect, callback); - } - } - } - - private void getAllNodes(Consumer callback) { - if (objects != null && !objects.isEmpty()) { - for (QuadTreeObject obj : objects) { - callback.accept(obj.data); - } - } - - if (childTL != null) { - childTL.getAllNodes(callback); - childTR.getAllNodes(callback); - childBL.getAllNodes(callback); - childBR.getAllNodes(callback); - } - } - - private void update(QuadTreeObject item) { - if (item.owner != null) { - item.owner.relocate(item); - } else { - relocate(item); - } - } - - public void toString(StringBuilder sb) { - for (int i = 0; i < level; i++) { - sb.append(" "); - } - sb.append(rect.toString()).append('\n'); - - if (objects != null) { - for (QuadTreeObject object : objects) { - for (int i = 0; i <= level; i++) { - sb.append(" "); - } - - sb.append(object.data.getId()).append('\n'); - } - } - - if (childTL != null) { - childTL.toString(sb); - childTR.toString(sb); - childBL.toString(sb); - childBR.toString(sb); - } - } - } - - private class QuadTreeNodesIterable implements NodeIterable { - - private final Rect2D searchRect; - - public QuadTreeNodesIterable(Rect2D searchRect) { - this.searchRect = searchRect; - } - - @Override - public Iterator iterator() { - return new QuadTreeNodesIterator(quadTreeRoot, searchRect); - } - - @Override - public Node[] toArray() { - final Collection collection = toCollection(); - return collection.toArray(new Node[collection.size()]); - } - - @Override - public Collection toCollection() { - final List list = new ArrayList<>(); - - final Iterator iterator = iterator(); - while (iterator.hasNext()) { - list.add(iterator.next()); - } - - return list; - } - - @Override - public void doBreak() { - readUnlock(); - } - - } - - private class QuadTreeNodesIterator implements Iterator { - - private final Rect2D searchRect; - private final Deque nodesStack = new ArrayDeque<>(); - private final Deque fullyContainedStack = new ArrayDeque<>(); - - // Current: - private Iterator currentIterator; - private boolean currentFullyContained = false; - private boolean finished = false; - - private QuadTreeObject next; - - public QuadTreeNodesIterator(QuadTreeNode root, Rect2D searchRect) { - this.searchRect = searchRect; - - readLock(); - - // Null rect means get all - currentFullyContained = searchRect == null; - - // We always add the root and don't test for the root being fully - // contained, to correctly handle the case of nodes out of the quad - // tree bounds - addChildrenToVisit(root, currentFullyContained); - currentIterator = root.objects != null ? root.objects.iterator() : null; - } - - private void addChildrenToVisit(QuadTreeNode quadTreeNode, boolean fullyContained) { - if (quadTreeNode.childTL != null) { - nodesStack.push(quadTreeNode.childBR); - nodesStack.push(quadTreeNode.childBL); - nodesStack.push(quadTreeNode.childTR); - nodesStack.push(quadTreeNode.childTL); - - fullyContainedStack.push(fullyContained); - fullyContainedStack.push(fullyContained); - fullyContainedStack.push(fullyContained); - fullyContainedStack.push(fullyContained); - } - } - - @Override - public boolean hasNext() { - if (finished) { - return false; - } - - if (next != null) { - return true; - } - - while (currentIterator != null || !nodesStack.isEmpty()) { - if (currentIterator != null) { - while (currentIterator.hasNext()) { - final QuadTreeObject elem = currentIterator.next(); - - if (currentFullyContained || searchRect.intersects(elem.minX, elem.minY, elem.maxX, elem.maxY)) { - next = elem; - return true; - } - } - - currentIterator = null; - } else { - final QuadTreeNode pointer = nodesStack.pop(); - - currentFullyContained = fullyContainedStack.pop() || searchRect.contains(pointer.rect); - - if (currentFullyContained || pointer.rect.intersects(searchRect)) { - addChildrenToVisit(pointer, currentFullyContained); - currentIterator = pointer.objects != null ? pointer.objects.iterator() : null; - } else { - currentIterator = null; - } - } - } - - readUnlock(); - finished = true; - return false; - } - - @Override - public Node next() { - if (next == null) { - throw new IllegalStateException("No next available!"); - } - - final Node node = next.data; - - next = null; - - return node; - } - - } -} diff --git a/store/src/main/java/org/gephi/graph/impl/TimestampIndexImpl.java b/store/src/main/java/org/gephi/graph/impl/TimestampIndexImpl.java deleted file mode 100644 index 2392961c..00000000 --- a/store/src/main/java/org/gephi/graph/impl/TimestampIndexImpl.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.impl; - -import it.unimi.dsi.fastutil.doubles.Double2IntMap; -import it.unimi.dsi.fastutil.doubles.Double2IntSortedMap; -import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import it.unimi.dsi.fastutil.objects.ObjectSet; -import org.gephi.graph.api.Element; -import org.gephi.graph.api.ElementIterable; -import org.gephi.graph.api.Interval; -import org.gephi.graph.api.types.TimestampMap; -import org.gephi.graph.api.types.TimestampSet; - -public class TimestampIndexImpl extends TimeIndexImpl> { - - public TimestampIndexImpl(TimeIndexStore> store, boolean main) { - super(store, main); - } - - @Override - public double getMinTimestamp() { - if (mainIndex) { - Double2IntSortedMap sortedMap = (Double2IntSortedMap) timestampIndexStore.timeSortedMap; - if (!sortedMap.isEmpty()) { - return sortedMap.firstDoubleKey(); - } - } else { - Double2IntSortedMap sortedMap = (Double2IntSortedMap) timestampIndexStore.timeSortedMap; - if (!sortedMap.isEmpty()) { - ObjectBidirectionalIterator bi = sortedMap.double2IntEntrySet().iterator(); - while (bi.hasNext()) { - Double2IntMap.Entry entry = bi.next(); - double timestamp = entry.getDoubleKey(); - int index = entry.getIntValue(); - if (index < timestamps.length) { - TimeIndexEntry timestampEntry = timestamps[index]; - if (timestampEntry != null) { - return timestamp; - } - } - } - } - } - return Double.NEGATIVE_INFINITY; - } - - @Override - public double getMaxTimestamp() { - if (mainIndex) { - Double2IntSortedMap sortedMap = (Double2IntSortedMap) timestampIndexStore.timeSortedMap; - if (!sortedMap.isEmpty()) { - return sortedMap.lastDoubleKey(); - } - } else { - Double2IntSortedMap sortedMap = (Double2IntSortedMap) timestampIndexStore.timeSortedMap; - if (!sortedMap.isEmpty()) { - ObjectBidirectionalIterator bi = sortedMap.double2IntEntrySet().iterator(sortedMap - .double2IntEntrySet().last()); - while (bi.hasPrevious()) { - Double2IntMap.Entry entry = bi.previous(); - double timestamp = entry.getDoubleKey(); - int index = entry.getIntValue(); - if (index < timestamps.length) { - TimeIndexEntry timestampEntry = timestamps[index]; - if (timestampEntry != null) { - return timestamp; - } - } - } - } - } - return Double.POSITIVE_INFINITY; - } - - @Override - public ElementIterable get(double timestamp) { - checkDouble(timestamp); - - readLock(); - Integer index = timestampIndexStore.timeSortedMap.get(timestamp); - if (index != null && index < timestamps.length) { - TimeIndexEntry ts = timestamps[index]; - if (ts != null) { - return new ElementIterableImpl(new ElementIteratorImpl(ts.elementSet.iterator())); - } - } - readUnlock(); - return ElementIterable.EMPTY; - } - - @Override - public ElementIterable get(Interval interval) { - checkDouble(interval.getLow()); - checkDouble(interval.getHigh()); - - readLock(); - ObjectSet elements = new ObjectOpenHashSet<>(); - Double2IntSortedMap sortedMap = (Double2IntSortedMap) timestampIndexStore.timeSortedMap; - if (!sortedMap.isEmpty()) { - for (Double2IntMap.Entry entry : sortedMap.tailMap(interval.getLow()).double2IntEntrySet()) { - double timestamp = entry.getDoubleKey(); - int index = entry.getIntValue(); - if (timestamp <= interval.getHigh()) { - if (index < timestamps.length) { - TimeIndexEntry ts = timestamps[index]; - if (ts != null) { - elements.addAll(ts.elementSet); - } - } - } else { - break; - } - } - } - if (!elements.isEmpty()) { - return new ElementIterableImpl(new ElementIteratorImpl(elements.iterator())); - } - readUnlock(); - return ElementIterable.EMPTY; - } -} diff --git a/store/src/main/java/org/gephi/graph/spi/package.html b/store/src/main/java/org/gephi/graph/spi/package.html deleted file mode 100644 index 1c426205..00000000 --- a/store/src/main/java/org/gephi/graph/spi/package.html +++ /dev/null @@ -1,3 +0,0 @@ - - SPI interfaces clients can implement to extend the API. - diff --git a/store/src/test/java/org/gephi/graph/impl/ConfigurationTest.java b/store/src/test/java/org/gephi/graph/impl/ConfigurationTest.java deleted file mode 100644 index b6e43f4d..00000000 --- a/store/src/test/java/org/gephi/graph/impl/ConfigurationTest.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.impl; - -import org.gephi.graph.api.Configuration; -import org.gephi.graph.api.TimeRepresentation; -import org.gephi.graph.api.types.IntervalDoubleMap; -import org.gephi.graph.api.types.TimestampDoubleMap; -import org.testng.Assert; -import org.testng.annotations.Test; - -public class ConfigurationTest { - - @Test - public void testDefault() { - Configuration c = new Configuration(); - Assert.assertNotNull(c.getNodeIdType()); - Assert.assertNotNull(c.getEdgeIdType()); - Assert.assertNotNull(c.getEdgeLabelType()); - Assert.assertNotNull(c.getEdgeWeightColumn()); - } - - @Test - public void testSetNodeIdType() { - Configuration c = new Configuration(); - c.setNodeIdType(Float.class); - Assert.assertEquals(c.getNodeIdType(), Float.class); - } - - @Test - public void testSetEdgeIdType() { - Configuration c = new Configuration(); - c.setEdgeIdType(Float.class); - Assert.assertEquals(c.getEdgeIdType(), Float.class); - } - - @Test - public void testSetEdgeLabelType() { - Configuration c = new Configuration(); - c.setEdgeLabelType(Float.class); - Assert.assertEquals(c.getEdgeLabelType(), Float.class); - } - - @Test - public void testSetEdgeWeightType() { - Configuration c = new Configuration(); - c.setEdgeWeightType(IntervalDoubleMap.class); - Assert.assertEquals(c.getEdgeWeightType(), IntervalDoubleMap.class); - c.setEdgeWeightType(TimestampDoubleMap.class); - Assert.assertEquals(c.getEdgeWeightType(), TimestampDoubleMap.class); - c.setEdgeWeightType(Double.class); - Assert.assertEquals(c.getEdgeWeightType(), Double.class); - } - - @Test - public void testSetTimeRepresentation() { - Configuration c = new Configuration(); - c.setTimeRepresentation(TimeRepresentation.INTERVAL); - Assert.assertEquals(c.getTimeRepresentation(), TimeRepresentation.INTERVAL); - } - - @Test - public void testSetEdgeWeightColumn() { - Configuration c = new Configuration(); - c.setEdgeWeightColumn(Boolean.FALSE); - Assert.assertEquals(c.getEdgeWeightColumn(), Boolean.FALSE); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testSetNodeIdTypeUnsupported() { - Configuration c = new Configuration(); - c.setNodeIdType(int[].class); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testSetEdgeIdTypeUnsupported() { - Configuration c = new Configuration(); - c.setEdgeIdType(int[].class); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testSetEdgeWeightTypeFloatUnsupported() { - Configuration c = new Configuration(); - c.setEdgeWeightType(Float.class); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testSetEdgeWeightTypeNotNumberUnsupported() { - Configuration c = new Configuration(); - c.setEdgeWeightType(String.class); - } - - @Test - public void testDefaultEquals() { - Assert.assertTrue(new Configuration().equals(new Configuration())); - } - - @Test - public void testDefaultHashCode() { - Assert.assertEquals(new Configuration().hashCode(), new Configuration().hashCode()); - } - - @Test - public void testEquals() { - Configuration c1 = new Configuration(); - Configuration c2 = new Configuration(); - c2.setNodeIdType(Float.class); - Assert.assertFalse(c1.equals(c2)); - } - - @Test - public void testHashCode() { - Configuration c1 = new Configuration(); - Configuration c2 = new Configuration(); - c2.setNodeIdType(Float.class); - Assert.assertNotEquals(c1.hashCode(), c2.hashCode()); - } - - @Test - public void testCopy() { - Configuration c1 = new Configuration(); - Configuration c2 = c1.copy(); - Assert.assertTrue(c1.equals(c2)); - c1.setNodeIdType(Float.class); - Assert.assertNotEquals(c2.getNodeIdType(), Float.class); - Assert.assertFalse(c1.equals(c2)); - } -} diff --git a/store/src/test/java/org/gephi/graph/impl/GraphLockTest.java b/store/src/test/java/org/gephi/graph/impl/GraphLockTest.java deleted file mode 100644 index 27e15400..00000000 --- a/store/src/test/java/org/gephi/graph/impl/GraphLockTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.impl; - -import org.testng.Assert; -import org.testng.annotations.Test; - -public class GraphLockTest { - - @Test - public void testReadUnlockAll() { - GraphLock lock = new GraphLock(); - lock.readLock(); - lock.readLock(); - Assert.assertEquals(lock.readWriteLock.getReadHoldCount(), 2); - lock.readUnlockAll(); - Assert.assertEquals(lock.readWriteLock.getReadLockCount(), 0); - } - - @Test - public void testWriteLockBeforeReadLock() { - GraphLock lock = new GraphLock(); - lock.writeLock(); - lock.readLock(); - lock.readLock(); - } - - @Test(expectedExceptions = IllegalMonitorStateException.class) - public void testWriteLockAfterReadLock() { - GraphLock lock = new GraphLock(); - lock.readLock(); - lock.writeLock(); - } - - @Test - public void testCheckHoldWriteLock() { - GraphLock lock = new GraphLock(); - lock.writeLock(); - lock.checkHoldWriteLock(); - } - - @Test(expectedExceptions = IllegalMonitorStateException.class) - public void testCheckHoldWriteLockFail() { - GraphLock lock = new GraphLock(); - lock.checkHoldWriteLock(); - } -} diff --git a/store/src/test/java/org/gephi/graph/impl/GraphViewImplTest.java b/store/src/test/java/org/gephi/graph/impl/GraphViewImplTest.java deleted file mode 100644 index b1c523d2..00000000 --- a/store/src/test/java/org/gephi/graph/impl/GraphViewImplTest.java +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Copyright 2012-2013 Gephi Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.gephi.graph.impl; - -import java.util.Arrays; -import org.gephi.graph.api.DirectedSubgraph; -import org.gephi.graph.api.Edge; -import org.gephi.graph.api.Graph; -import org.gephi.graph.api.GraphFactory; -import org.gephi.graph.api.GraphView; -import org.gephi.graph.api.Node; -import org.gephi.graph.api.UndirectedSubgraph; -import org.testng.Assert; -import org.testng.annotations.Test; - -public class GraphViewImplTest { - - @Test - public void testFill() { - GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(); - - DirectedSubgraph graph = store.getDirectedGraph(view); - UndirectedSubgraph unGraph = store.getUndirectedGraph(view); - view.fill(); - - Assert.assertEquals(view.getNodeCount(), graphStore.getNodeCount()); - Assert.assertEquals(view.getEdgeCount(), graphStore.getEdgeCount()); - for (Edge e : graphStore.getEdges()) { - Assert.assertTrue(graph.contains(e)); - } - for (Node n : graphStore.getNodes()) { - Assert.assertTrue(graph.contains(n)); - } - for (int i = 0; i < graphStore.edgeTypeStore.length; i++) { - Assert.assertEquals(graph.getEdgeCount(i), graphStore.getEdgeCount(i)); - } - for (Edge e : graphStore.undirectedDecorator.getEdges()) { - Assert.assertTrue(unGraph.contains(e)); - } - for (Node n : graphStore.undirectedDecorator.getNodes()) { - Assert.assertTrue(unGraph.contains(n)); - } - for (int i = 0; i < graphStore.edgeTypeStore.length; i++) { - Assert.assertEquals(unGraph.getEdgeCount(i), graphStore.undirectedDecorator.getEdgeCount(i)); - } - } - - @Test - public void testMainView() { - GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); - GraphViewImpl view = new GraphViewStore(graphStore).createView(); - - Assert.assertFalse(view.isMainView()); - } - - @Test - public void testAddNodeMainView() { - GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(); - - NodeImpl node = new NodeImpl("A"); - graphStore.addNode(node); - - Assert.assertTrue(view.nodeBitVector.size() >= node.storeId); - boolean a = view.addNode(node); - Assert.assertTrue(a); - Assert.assertTrue(view.containsNode(node)); - } - - @Test - public void testAddEdgeMainView() { - GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(); - - NodeImpl source = new NodeImpl("A"); - NodeImpl target = new NodeImpl("B"); - graphStore.addNode(source); - graphStore.addNode(target); - view.addNode(source); - view.addNode(target); - - EdgeImpl edge = new EdgeImpl("S", source, target, 0, 1.0, true); - graphStore.addEdge(edge); - - Assert.assertTrue(view.edgeBitVector.size() >= edge.storeId); - boolean a = view.addEdge(edge); - Assert.assertTrue(a); - Assert.assertTrue(view.containsEdge(edge)); - } - - @Test - public void testViewDeepEquals() { - GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(); - - NodeImpl n1 = graphStore.getNode("0"); - view.addNode(n1); - - Assert.assertTrue(view.deepEquals(view)); - - GraphViewImpl view2 = store.createView(); - - NodeImpl n2 = graphStore.getNode("0"); - view2.addNode(n2); - - Assert.assertTrue(view.deepEquals(view2)); - } - - @Test - public void testViewDeepHashCode() { - GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(); - - NodeImpl n1 = graphStore.getNode("0"); - view.addNode(n1); - - Assert.assertEquals(view.hashCode(), view.hashCode()); - - GraphViewImpl view2 = store.createView(); - - NodeImpl n2 = graphStore.getNode("0"); - view2.addNode(n2); - - Assert.assertEquals(view.deepHashCode(), view2.deepHashCode()); - } - - @Test - public void testViewIntersection() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(); - GraphViewImpl view2 = store.createView(); - - EdgeImpl e1 = graphStore.getEdge("0"); - EdgeImpl e2 = graphStore.getEdge("5"); - NodeImpl n1 = e1.getSource(); - NodeImpl n2 = e1.getTarget(); - NodeImpl n3 = e2.getSource(); - NodeImpl n4 = e2.getTarget(); - view.addNode(n1); - view2.addNode(n1); - view.addNode(n2); - view2.addNode(n2); - - view.addNode(n3); - view.addNode(n4); - - view.addEdge(e1); - view2.addEdge(e1); - view.addEdge(e2); - - view.intersection(view2); - - Assert.assertTrue(view.containsNode(n1)); - Assert.assertTrue(view.containsNode(n2)); - Assert.assertTrue(view.containsEdge(e1)); - Assert.assertFalse(view.containsNode(n3)); - Assert.assertFalse(view.containsNode(n4)); - Assert.assertFalse(view.containsEdge(e2)); - - Assert.assertTrue(view2.deepEquals(view)); - } - - @Test - public void testViewIntersectionEdgeView() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(false, true); - GraphViewImpl view2 = store.createView(false, true); - - view.fill(); - view2.fill(); - - EdgeImpl e1 = graphStore.getEdge("0"); - EdgeImpl e2 = graphStore.getEdge("5"); - - view.removeEdge(e1); - view2.removeEdge(e2); - - view.intersection(view2); - - Assert.assertFalse(view.containsEdge(e1)); - Assert.assertFalse(view.containsEdge(e2)); - } - - @Test - public void testViewIntersectionNodeView() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(true, false); - GraphViewImpl view2 = store.createView(); - - view.fill(); - view2.fill(); - - EdgeImpl e1 = graphStore.getEdge("0"); - EdgeImpl e2 = graphStore.getEdge("5"); - NodeImpl s1 = e1.getSource(); - - view2.removeNode(s1); - view2.removeEdge(e2); - - view.intersection(view2); - - Assert.assertFalse(view.containsEdge(e1)); - Assert.assertFalse(view.containsNode(s1)); - Assert.assertTrue(view.containsEdge(e2)); - } - - @Test - public void testViewUnion() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(); - GraphViewImpl view2 = store.createView(); - - EdgeImpl e1 = graphStore.getEdge("0"); - EdgeImpl e2 = graphStore.getEdge("5"); - NodeImpl n1 = e1.getSource(); - NodeImpl n2 = e1.getTarget(); - NodeImpl n3 = e2.getSource(); - NodeImpl n4 = e2.getTarget(); - view.addNode(n1); - view.addNode(n2); - - view2.addNode(n3); - view2.addNode(n4); - - view.addEdge(e1); - view2.addEdge(e2); - - view.union(view2); - - Assert.assertTrue(view.containsNode(n1)); - Assert.assertTrue(view.containsNode(n2)); - Assert.assertTrue(view.containsEdge(e1)); - Assert.assertTrue(view.containsNode(n3)); - Assert.assertTrue(view.containsNode(n4)); - Assert.assertTrue(view.containsEdge(e2)); - } - - @Test - public void testViewUnionEdgeView() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(false, true); - GraphViewImpl view2 = store.createView(false, true); - - EdgeImpl e1 = graphStore.getEdge("0"); - EdgeImpl e2 = graphStore.getEdge("5"); - - view.addEdge(e1); - view2.addEdge(e2); - - view.union(view2); - - Assert.assertTrue(view.containsEdge(e1)); - Assert.assertTrue(view.containsEdge(e2)); - } - - @Test - public void testViewUnionNodeView() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(true, false); - GraphViewImpl view2 = store.createView(true, true); - - EdgeImpl e1 = graphStore.getEdge("0"); - EdgeImpl e2 = graphStore.getEdge("5"); - NodeImpl n1 = e1.getSource(); - NodeImpl n2 = e1.getTarget(); - NodeImpl n3 = e2.getSource(); - NodeImpl n4 = e2.getTarget(); - - view2.addAllNodes(Arrays.asList(new NodeImpl[] { n1, n2, n3, n4 })); - view2.addEdge(e1); - Assert.assertFalse(view.containsEdge(e2)); - - view.union(view2); - - Assert.assertTrue(view.containsEdge(e1)); - Assert.assertTrue(view.containsEdge(e2)); - } - - @Test - public void testViewNot() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(); - - view.not(); - - for (Node n : graphStore.getNodes()) { - Assert.assertTrue(view.containsNode((NodeImpl) n)); - } - for (Edge e : graphStore.getEdges()) { - Assert.assertTrue(view.containsEdge((EdgeImpl) e)); - } - Assert.assertEquals(view.getNodeCount(), graphStore.getNodeCount()); - Assert.assertEquals(view.getEdgeCount(), graphStore.getEdgeCount()); - - view.not(); - - Assert.assertEquals(view.getNodeCount(), 0); - Assert.assertEquals(view.getEdgeCount(), 0); - - EdgeImpl e1 = graphStore.getEdge("0"); - NodeImpl n1 = e1.getSource(); - NodeImpl n2 = e1.getTarget(); - - view.addNode(n1); - view.addNode(n2); - view.addEdge(e1); - - view.not(); - - Assert.assertFalse(view.containsNode(n1)); - Assert.assertFalse(view.containsNode(n2)); - Assert.assertFalse(view.containsEdge(e1)); - } - - @Test - public void testViewNotInterEdges() { - GraphStore graphStore = new GraphModelImpl().store; - GraphFactory factory = graphStore.factory; - Node n1 = factory.newNode(); - Node n2 = factory.newNode(); - Node n3 = factory.newNode(); - graphStore.addAllNodes(Arrays.asList(new Node[] { n1, n2, n3 })); - Edge e1 = factory.newEdge(n1, n2, false); - Edge e2 = factory.newEdge(n1, n3, false); - graphStore.addAllEdges(Arrays.asList(new Edge[] { e1, e2 })); - - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(); - view.fill(); - Graph viewGraph = store.getGraph(view); - viewGraph.removeNode(n3); - - view.not(); - - Assert.assertEquals(viewGraph.getNodeCount(), 1); - Assert.assertEquals(viewGraph.getEdgeCount(), 0); - } - - @Test - public void testViewNotNodeView() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(true, false); - - EdgeImpl e1 = graphStore.getEdge("0"); - NodeImpl n1 = e1.getSource(); - NodeImpl n2 = e1.getTarget(); - - view.addNode(n1); - view.addNode(n2); - - view.not(); - - Assert.assertFalse(view.containsNode(n1)); - Assert.assertFalse(view.containsNode(n1)); - Assert.assertFalse(view.containsEdge(e1)); - - view.not(); - - Assert.assertTrue(view.containsNode(n1)); - Assert.assertTrue(view.containsNode(n2)); - Assert.assertTrue(view.containsEdge(e1)); - } - - @Test - public void testNodeView() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(true, false); - - for (Node n : graphStore.getNodes()) { - view.addNode(n); - - Assert.assertTrue(view.containsNode((NodeImpl) n)); - for (Edge e : graphStore.getEdges(n)) { - Node opposite = graphStore.getOpposite(n, e); - if (view.containsNode((NodeImpl) opposite)) { - Assert.assertTrue(view.containsEdge((EdgeImpl) e)); - } - } - } - } - - @Test - public void testNodeViewEdgeUpdate() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphViewStore store = graphStore.viewStore; - GraphViewImpl view = store.createView(true, false); - - NodeImpl n1 = graphStore.getNode("0"); - NodeImpl n2 = graphStore.getNode("1"); - - view.addNode(n1); - view.addNode(n2); - - Assert.assertNull(graphStore.getEdge(n1, n2)); - EdgeImpl edge = (EdgeImpl) graphStore.factory.newEdge("edge", n1, n2, 0, 1.0, true); - graphStore.addEdge(edge); - - Assert.assertTrue(view.containsEdge(edge)); - } - - @Test - public void testIsNodeView() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphViewStore store = graphStore.viewStore; - - GraphView v1 = store.createView(); - GraphView v2 = store.createView(true, false); - - Assert.assertTrue(v1.isNodeView() && v1.isEdgeView()); - Assert.assertTrue(v2.isNodeView() && !v2.isEdgeView()); - } - - @Test - public void testIsEdgeView() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphViewStore store = graphStore.viewStore; - - GraphView v1 = store.createView(); - GraphView v2 = store.createView(false, true); - - Assert.assertTrue(v1.isNodeView() && v1.isEdgeView()); - Assert.assertTrue(!v2.isNodeView() && v2.isEdgeView()); - } - - @Test - public void testDefaultVisibleView() { - GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); - GraphView view = graphStore.viewStore.getVisibleView(); - - Assert.assertNotNull(view); - Assert.assertEquals(view, graphStore.mainGraphView); - } -} diff --git a/store/src/test/java/org/gephi/graph/impl/NodesQuadTreeTest.java b/store/src/test/java/org/gephi/graph/impl/NodesQuadTreeTest.java deleted file mode 100644 index 7b781f2f..00000000 --- a/store/src/test/java/org/gephi/graph/impl/NodesQuadTreeTest.java +++ /dev/null @@ -1,155 +0,0 @@ -package org.gephi.graph.impl; - -import java.util.Arrays; -import java.util.Collection; -import org.gephi.graph.api.Node; -import org.gephi.graph.api.NodeIterable; -import org.gephi.graph.api.Rect2D; -import org.testng.Assert; -import org.testng.annotations.Test; - -public class NodesQuadTreeTest { - - private static final float BOUNDS = 1e6f; - private static final Rect2D BOUNDS_RECT = new Rect2D(-BOUNDS, -BOUNDS, BOUNDS, BOUNDS); - - @Test - public void testGetAll() { - final NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); - - Node n1 = new NodeImpl("1"); - n1.setPosition(100, 100); - - Node n2 = new NodeImpl("2"); - n2.setPosition(0, 0); - - Node n3 = new NodeImpl("3"); - n2.setPosition(-100, -100); - - q.addNode(n1); - q.addNode(n2); - q.addNode(n3); - - Collection all = q.getAllNodes().toCollection(); - Assert.assertEquals(all.size(), 3); - - Collection rectContainingAll = q.getNodes(BOUNDS_RECT).toCollection(); - Assert.assertEquals(rectContainingAll, all); - - Collection bigRectContainingAll = q.getNodes(-BOUNDS * 2, -BOUNDS * 2, BOUNDS, BOUNDS).toCollection(); - Assert.assertEquals(bigRectContainingAll, all); - } - - @Test - public void testOutOfBoundsStillWorks() { - final NodesQuadTree q = new NodesQuadTree(0, 0, 10, 10); - - Node n1 = new NodeImpl("1"); - n1.setPosition(100, 100); - n1.setSize(10); - - Node n2 = new NodeImpl("2"); - n2.setPosition(0, 0); - n2.setSize(5); - - Node n3 = new NodeImpl("3"); - n3.setPosition(-100, -100); - n3.setSize(3); - - q.addNode(n1); - q.addNode(n2); - q.addNode(n3); - - Collection all = q.getAllNodes().toCollection(); - Assert.assertEquals(all.size(), 3); - - assertEmpty(q.getNodes(80, 80, 89.99f, 89.99f)); - - assertSame(q.getNodes(95, 95, 99, 99), n1); - assertSame(q.getNodes(0, 0, 101, 101), n1, n2); - assertSame(q.getNodes(4, 4, 91, 91), n1, n2); - } - - @Test - public void testGetZone1() { - final NodesQuadTree q = new NodesQuadTree(BOUNDS_RECT); - - Node n1 = new NodeImpl("1"); - n1.setPosition(100, 100); - n1.setSize(10); - - Node n2 = new NodeImpl("2"); - n2.setPosition(0, 0); - n2.setSize(5); - - Node n3 = new NodeImpl("3"); - n3.setPosition(-100, -100); - n3.setSize(3); - - q.addNode(n1); - q.addNode(n2); - q.addNode(n3); - - assertEmpty(q.getNodes(80, 80, 89.99f, 89.99f)); - - assertSame(q.getNodes(95, 95, 99, 99), n1); - assertSame(q.getNodes(0, 0, 101, 101), n2, n1); - assertSame(q.getNodes(4, 4, 91, 91), n2, n1); - } - - @Test - public void testGetZone2() { - final NodesQuadTree q = new NodesQuadTree(-120, -120, 120, 120); - - Node n1 = new NodeImpl("1"); - n1.setPosition(100, 100); - n1.setSize(10); - - Node n2 = new NodeImpl("2"); - n2.setPosition(0, 0); - n2.setSize(5); - - Node n3 = new NodeImpl("3"); - n3.setPosition(-100, -100); - n3.setSize(3); - - q.addNode(n1); - q.addNode(n2); - q.addNode(n3); - - assertEmpty(q.getNodes(80, 80, 89.99f, 89.99f)); - - assertSame(q.getNodes(95, 95, 99, 99), n1); - assertSame(q.getNodes(0, 0, 101, 101), n2, n1); - assertSame(q.getNodes(4, 4, 91, 91), n2, n1); - } - - private void assertSame(NodeIterable iterable, Collection expected) { - Collection found = iterable.toCollection(); - try { - Assert.assertEquals(found, expected); - } catch (AssertionError ex) { - System.out.println("Found: " + listIds(found)); - System.out.println("Expected: " + listIds(expected)); - throw ex; - } - } - - private void assertSame(NodeIterable iterable, Node... expected) { - assertSame(iterable, Arrays.asList(expected)); - } - - private void assertEmpty(NodeIterable iterable) { - Assert.assertEquals(iterable.toCollection().size(), 0); - } - - private String listIds(Collection nodes) { - StringBuilder sb = new StringBuilder(); - - for (Node node : nodes) { - sb.append(node.getId()).append(' '); - } - - return sb.toString(); - } -} diff --git a/store/src/travis/pubring.gpg.enc b/store/src/travis/pubring.gpg.enc deleted file mode 100644 index 85e73bf7..00000000 --- a/store/src/travis/pubring.gpg.enc +++ /dev/null @@ -1,26 +0,0 @@ -U2FsdGVkX18g16na409X0Wa6FPFPgFyiT1HWT4D0MdDKVOnTvTGDPYxJms3aFmn1 -VZBbYpuHgjFsWUK9Zd8WA3MDyzcCAQ9HZ0KuZGJnFeZHKl5gcT3P3IoEw4E2ZQn6 -7G0GBYMDnVc3Hf4fy5U2QXoUFCwuYePryYdLAhpT6LacscJ2NFnpa3U9UDvnLDOQ -EXUl6nisXgqs4Hrs4n+Fp3vTXHCEDaUqIhI65XgR6oZNJD/iVeveDIk3AD4B5w6+ -6EjWU3Hsr1x3mZTdO8hxpb+IqxNzkyE2eYe+XF5LBDG6wTxlvB2lDp52VGmiWlC6 -WKq8jF3oUuXWNwCvvdTa5bRiYFIK3JrnidZFURZBC0K3+tlS3RDSUeqKytxdjHUU -VNehXZvACxodhKtKx4GfsDGt6aWmHSlJlBjg6DnnPNhIt+Es9/DAJRRG+KK4ob0L -tLgDy8aSgw2avNztpCwXL6GIG7yhrbVNJcCkL8zaljyp9fjqithvxFRa0PGP2b2Z -4n5ek8/n9a/uDTE+FuiMz+GCw3rx2YMfKIIrkISmPvVg4vT2nGmV4c8T9CCK7JZE -vI7g510PAJ8EDMYDCuBhCONeEJhK+odrZhXDEZ+tKwUkdnSWioI/OFdywc4kb53Z -lpAKJjJXUi54SwrCLH5oPmfGfUwupd3KLIjMDmRWhvL/r7Qsbs6AEgKtkwyPwwLn -r8KiLHVPFgnbkatXRM4teNX2aSwhnAJ8LhBA073fcg5+WRlUOtWMmAISDGCA2qtA -kzV0C9etAcmUc/7wb1TuiIF0FPR/YeiSorajXCSMH393Rh61IfxExG2CRZZpvN+1 -aFJoQIrNEibDSJRU8tRivMdB0BB56fyvm9SwVAOUKhdBg1h0LTM/0Ee147I3xLq8 -CDX2KHDSbegPF9yV+seauDNhF7ZQlAQA95J1ktxiwtouJWi4ZfaSCWezDxo8bA24 -qJq+1T5dgyECEXx85VIxJU4RcEQJC4FN+KaAGZxwOz1M+zRaKZTPbzDadq26j+PI -zrBxQ8Xxbcip1JDQ9TRLT7NVfTwvoIwAAm6GjYQH6bCXbxplH60cmFQQJHlklf92 -kGQxmOyqcIvFl+nzWDcaiVbTw/rLvmMmtwcENskx8O+L7SCBU2s7RDHXDwb5TeM/ -92xaulFivlF6brqIhQO6XHoVulLYZ3y1s/1igxKfsgwcnSjpYFrLq8XwxKA+h9NH -KJMuT4gf5IqH21SXt1gr1MquBxSlXSACyTWX1MaHIGuegKLIYIwDzXyZyCmtDjcq -Rc/xkrKQ+g9JVTD1oMJC5Srifp7WoLVRpj2pq2RsWaA6Ys+xGM27Gcoumb9MRYdH -3PGjeXzf7fbrFKm9tgQ47vU54iLJaGzN93buQe7DNLe7a9IjFJfu2Hw/0zxbVMmp -JLw1CYCzHWtzG08HGXEHJ31oQxsm9eGJSBYwVVYi1SS37vye8eYd7JT7H8NBck15 -k8s8lrCf8V1yhUEVqv5RQys8uha9gqYA5egRq8ZkX/Pqcqdo+ZZfvM2+jsNrukDG -oTH1ix676EWN082MQuB/YCTd2uDmRF+4DSa8DvH7SPgx0e5cQYx7q53wyGmw6vrF -04qRL0KmOG59GMQ9qOGL/Q== diff --git a/store/src/travis/secretring.gpg.enc b/store/src/travis/secretring.gpg.enc deleted file mode 100644 index 41b749b0..00000000 --- a/store/src/travis/secretring.gpg.enc +++ /dev/null @@ -1,55 +0,0 @@ -U2FsdGVkX1+cuX4jSQeTw8x9YJgTSrhQxzABFFHS5OSdgrKi+JgDGjreaQe+SSQ4 -hgyZ+TO3mLSKnNsXOnSooQzWmi7VjXWEl7BR0k+s5DB5bGxzrmwvmU0wMrJbOm0k -7vQJGgkZn/wn6RSA33pbEKW3NYWN5oSt4ZsYVP5ecYF/feAm7dJ5ZAncSAMQw2Gy -71qWuKQcVHqlhM/My4hw40aO2dLN79Y/Wzi5hRhxndBX2kio4QPwxpib/zw4jz4v -1+lhzlUuAXQdnPp90Cbq7kn48wxVhat+aP6cwO6NTvzlRw0fgdjfKRgm9J42zMf1 -BroUPCMMQclEnEqDcW8Ti7DgP818YY/h3ghPYMu14upfpCxXzUeI5RGnGR9syfyn -7xMur6fCF31BCOoz/BsLsUys+sxB2TcDEpk+6/sbomkhvvM/LOYtGpGitqLOzzWm -Yv3mMUpb2ls5GEiPC10iTyryhhhryLc9wtHMawOYpz2u5GL9+B5n0Wcem8FjKpze -WGwTlXoCDH3kcxWkFz6up4mWIM6/zl3mkUFqy+S961Iv3EVxTwypql2Rn0e6l5RO -2ilheg9ikJ/psehjtydRMqndG3eXCPLZYV/iMwST4DboGnDckcqnLlsThIQkA473 -4jGJLDPxDVCU3jBnQcRqi0bd4eExfTrV5sTgGaTeIyn/QSSz5RV7Nfc2QEXoz9EN -ZZwdTNnXfrD/y5HXO71SKKQtSsTUE2rxHBBr0x7oAZCzaOys2wpww2qEyvMYZ+H+ -3NZZs3nyKJXOPeJ20G4uLgnOp9nNhv06UPrUfMPtX8HygSLqg0/naiI2rc3wPrjC -rFzZNM7QblPRATWU9239coPHPmSlTfB6KqwtOS34BAG8jnRqjH2zzZO7P6pNf8xk -RHHe9pbeIwpfHnul+x7mHzVP2gGNKInEYR5ekeDndlHEa9SVI5nVxXcoTBpDQdZz -rywsMgcPxps1wbz1nRnjbQm7mbo5fL/b06V62nSSu/pHzKHzQgc1AoOtL1N+t1l1 -l8y3KCl0T5/MQgAfMO7HuGmmfZfZ5VibmMH4H8GOdRxQzV+OyQ5xsLZCsi1BTXAW -UzGIrKURmnBwI8937jct6Vr0LOVfN0G6QuBS/IDwzyfpIYZYVAR65JWeeekTVpDL -LVxIVBQb9ETLwROYBOwfzSw2bZNNueYEOG9wWZqnNOlsf29OOsSi4dalIBf7wBfd -lAjS3PZgt2v5/RXIKZ6pXP+ZDWUl6Fea7Mpy0a0P5v/93qUrk0ssO2CCv3qYZKds -wzR3KzKVifMrPvZn4pl3fQvNe91tKQ1jX/cYbqntXY2H2Yz+4ZmFQpFqYVpupz/3 -/X89JNUl6B6izAo6T8DlrFxQPlKZN8vxAAKxXXb95YOObEvc/8yYVXSVzzG/XZrs -wOek4MGBoshJuUOw1GByG89Jvbjaq8wjJ+3SfcygsMYL1AUySoOllWzfxZjNK8yN -b7RP5YK4MpVH5vTRhCXM64GpaRpGsJ4Ovz2bdr9bg00DK8MRGWo58cM25ApP1p40 -rmr78yLQ0HnVztERwp81Uo1SskyGxTR+Txs7ylztuezRG4bHFJ4sI18SKBgjg0xf -ukQ2prSXP3U9nBPfpJovOPAbp9mUc03u1W0aPV9X+dOmGVvAon/ZKm7/VyLRmpk4 -3jUbUO9OnxsF7Nvpc0FwT1ns4cYPBJo0W/3Xt62oZAkQ9Qll6xWhsxaSJslwhddN -skgAyWJfx/LcTrpFWXNpSH7et7SMz6poqmhOQbIq4gmP4MVRrq2mch+5vBqjsG40 -Sl/Tx8yiX3unl4ugin9iLeZ0kBJ0OQj+99KRyTsLmBUDb320bgHSp4QKD8WgKJoM -tu6U39trNpWYvtT5L9vESP2fwyNR/xF4d/W3Og/IH2CZvd62Bl+K7maC3D0Y7LcB -K5U2tHG+EXmttZ+pLomvU+1X0tkRzEtw9DC6jaAkvPPPqjf68zfBg2YkbZlhrH9l -PNrjMj96o9XOvZtnQhkwfm8PFtgpcQ+L6UjWjrwZNia9bj+gdxWpm3odunQlzMvq -ZclFgwi0nIDxSPq/Soag4ZqSa6ow2EkbGd6gXUnWJFgiEDCykzWDDp1zX6CX8Eoz -h5DVIqJUTFfvldE2HyOJoXwmeai/MYyyk8EJiMoluycen2zbL9QHBtbA0Gx5Yjr8 -iQpqBeJiRsiPnVubbLquIQQ/vEElAlm6wkqrtlTHUP5726RIYesZpAmdChPtuse6 -7ch/V37Sbc0tkWLBdcb9tHbo5mrIVNK9Fz7f/NpBHc7ONK8LSQFfwrRkFsiaTq8l -R7SmJo+Wo/5aeS/6hK10+oWLGipt1a8rmdUri+x16tICY7mlg5hm6voeqG13Iif5 -rBmQAieeNo5jjBE1Hrovu9Uv1kh/LSKOdRj3r0XFfTVuj+uiE667rW6AsRT/f/Hk -l+gUfh0/BV6Wqw/U5GUIQykEcUM1msGq7z1US+C35dkWG8t7YqEPtxPVEeuej/wg -B8u8jPhiTvYWszz+ELMTvDIIqlw0XKGxO1eDVM2S2K/0dDDZwC3CaBgGq3br+vcK -PTzXVeDSKYICEAseF5307DmyMh4wpIhXANU3sblRLJUow/ySVgRw/UAQ8yLNZmr0 -kljKrRDohZ674sli1l++T5Q2DZ4zUTWSddXeW8B1mTRumkjwFyLSrxh4MoAjIDuT -k5Jjr/q8hXGgoFlglCx4K/sNKDl3JqpTSwT3VMTxU6A4cyowEuy947OXFsqektmJ -44+nXzv9L2CAZvGT98wGA7FekAzYTWHicIJAF3CZTfNVobU7wCSISE+oZS1pmeWj -af0/8ktNNR2pt3j3b3Fwxm9oeGPJoD+7QRwrQkSJKkWFhgZsiorcon7y3rDmWfUM -TNkgpNjvZVh7p/y5vougAcTf/hAIp2Jd5U2flXQOcDy47u7c8uX10ldH1WdYAWlW -uVSjN7TaZhQSRC84V75ET0MpAUwPVXXTLbwAZW+TjLcCl4zlK07rFogadxlFfYGB -c6KVQ42eSy1HByNLD31U8kM3MO7F0snuvyXyKxzh2wFT6lsriqGDsneVomb7Fe1M -FehhgOksVc1+YDqbh7GMH+U5jjBm7pTlnKD8uHalNlutixUTrrMP8EAf14zjf9YJ -KqdG4ZyW7eb++OiajgtQDIZGIry8v1TCLTeX4RRcRPHyaF5sKduMM5XOpJ3pxhX5 -miyP11m/jhMHCGZwQ934PRV7/NosNKC9MLHHI6cpMjjV1/+IoYNAfKo7+SIM1MdW -fKvv162rGhl880fnqlTVdlDCJhxumXHakM/t2yb4/oQ2JRm5n9BdRsZs8oGJ3wAP -rlHa3fmHH0HB3dVws3ZkzAyXVe5GzRe2t4XG8uoQdUrIiiAO6eOKtlw6pxHsidJP -XUQ5QDDH8PbcuWgqxhDyL2AKu/ODlwz3DUWRxgxtJYbF6TulSlS9t6nnUtxO3Xu9 -HLSC/M5EDpnHfEbxkOrJyQ==