Merge pull request #2628 from apache/intro-commons-v2

**TODO:**
After this PR merged, change the README in commons & pin a [issue](https://github.com/hugegraph/hugegraph-hubble/issues/371) to notify all users/devs to know the context (also update the repo description...)

Finally, we may need start a discussion for marking commons repo in achieved status
This commit is contained in:
imbajin 2024-08-22 19:07:20 +08:00 committed by GitHub
commit 0a0f2c3bfa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
259 changed files with 34212 additions and 30 deletions

View File

@ -42,7 +42,8 @@ github:
- Analyze (java)
- CodeQL
- check-license
- build (memory, 11)
- build-server (memory, 11)
- build-commons (11)
required_pull_request_reviews:
dismiss_stale_reviews: true
require_code_owner_reviews: false

1
.gitattributes vendored
View File

@ -12,4 +12,5 @@ hugegraph-store/hg-store-dist/src/assembly/static/bin/libjemalloc_aarch64.so exp
.github/ export-ignore
.idea/ export-ignore
install-dist/scripts/ export-ignore
hugegraph-commons/hugegraph-dist/ export-ignore
docker/ export-ignore

View File

@ -32,7 +32,7 @@ jobs:
- name: mvn install
run: |
mvn install -DskipTests=true -ntp
mvn install -Dmaven.test.skip=true -ntp
- name: generate current dependencies
run: |
bash $SCRIPT_DEPENDENCY/regenerate_known_dependencies.sh current-dependencies.txt

64
.github/workflows/commons-ci.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: "HugeGraph-Commons CI"
on:
workflow_dispatch:
push:
branches:
- master
- /^release-.*$/
- /^test-.*$/
pull_request:
jobs:
build-commons:
runs-on: ubuntu-latest
env:
# TODO: reset use stage to false later
USE_STAGE: 'true' # Whether to include the stage repository.
strategy:
fail-fast: false
matrix:
JAVA_VERSION: ['11']
steps:
- name: Install JDK ${{ matrix.JAVA_VERSION }}
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.JAVA_VERSION }}
distribution: 'zulu'
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 2
- name: Use staged maven repo settings
if: ${{ env.USE_STAGE == 'true' }}
run: |
cp $HOME/.m2/settings.xml /tmp/settings.xml
cp -vf .github/configs/settings.xml $HOME/.m2/settings.xml && cat $HOME/.m2/settings.xml
- name: Compile
run: |
mvn compile -Dmaven.javadoc.skip=true -ntp
- name: Run common test
run: |
mvn test -pl hugegraph-commons/hugegraph-common -Dtest=UnitTestSuite
- name: Run rpc test
run: |
mvn test -pl hugegraph-commons/hugegraph-rpc -Dtest=UnitTestSuite
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3.0.0
with:
file: target/jacoco.xml

View File

@ -1,4 +1,4 @@
name: "Graph PD & Store & Hstore CI"
name: "HugeGraph-PD & Store & Hstore CI"
on:
push:
@ -14,7 +14,8 @@ jobs:
runs-on: ubuntu-latest
env:
# TODO: avoid duplicated env setup in pd & store
USE_STAGE: 'false' # Whether to include the stage repository.
# TODO: reset use stage to false later
USE_STAGE: 'true' # Whether to include the stage repository.
# TODO: remove outdated env
TRAVIS_DIR: hugegraph-server/hugegraph-dist/src/assembly/travis
REPORT_DIR: target/site/jacoco
@ -46,11 +47,11 @@ jobs:
- name: Run common test
run: |
mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-common-test
mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-common-test -DskipCommonsTests=true
- name: Run core test
run: |
mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-core-test
mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-core-test -DskipCommonsTests=true
# The above tests do not require starting a PD instance.
@ -64,11 +65,11 @@ jobs:
- name: Run client test
run: |
mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-client-test
mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-client-test -DskipCommonsTests=true
- name: Run rest test
run: |
mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-rest-test
mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-rest-test -DskipCommonsTests=true
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3.0.0
@ -79,7 +80,7 @@ jobs:
# TODO: avoid duplicated env setup
runs-on: ubuntu-latest
env:
USE_STAGE: 'false' # Whether to include the stage repository.
USE_STAGE: 'true' # Whether to include the stage repository.
# TODO: remove outdated env
TRAVIS_DIR: hugegraph-server/hugegraph-dist/src/assembly/travis
REPORT_DIR: target/site/jacoco
@ -120,27 +121,27 @@ jobs:
- name: Run common test
run: |
mvn test -pl hugegraph-store/hg-store-test -am -P store-common-test
mvn test -pl hugegraph-store/hg-store-test -am -P store-common-test -DskipCommonsTests=true
- name: Run client test
run: |
mvn test -pl hugegraph-store/hg-store-test -am -P store-client-test
mvn test -pl hugegraph-store/hg-store-test -am -P store-client-test -DskipCommonsTests=true
- name: Run core test
run: |
mvn test -pl hugegraph-store/hg-store-test -am -P store-core-test
mvn test -pl hugegraph-store/hg-store-test -am -P store-core-test -DskipCommonsTests=true
- name: Run rocksdb test
run: |
mvn test -pl hugegraph-store/hg-store-test -am -P store-rocksdb-test
mvn test -pl hugegraph-store/hg-store-test -am -P store-rocksdb-test -DskipCommonsTests=true
- name: Run server test
run: |
mvn test -pl hugegraph-store/hg-store-test -am -P store-server-test
mvn test -pl hugegraph-store/hg-store-test -am -P store-server-test -DskipCommonsTests=true
- name: Run raft-core test
run: |
mvn test -pl hugegraph-store/hg-store-test -am -P store-raftcore-test
mvn test -pl hugegraph-store/hg-store-test -am -P store-raftcore-test -DskipCommonsTests=true
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3.0.0

View File

@ -1,4 +1,4 @@
name: "Graph Server CI"
name: "HugeGraph-Server CI"
on:
push:
@ -9,11 +9,13 @@ on:
pull_request:
jobs:
# TODO: rename to build-server later
build:
# TODO: we need test & replace it to ubuntu-24.04 or ubuntu-latest
runs-on: ubuntu-20.04
env:
USE_STAGE: 'false' # Whether to include the stage repository.
# TODO: reset use stage to false later
USE_STAGE: 'true' # Whether to include the stage repository.
TRAVIS_DIR: hugegraph-server/hugegraph-dist/src/assembly/travis
REPORT_DIR: target/site/jacoco
BACKEND: ${{ matrix.BACKEND }}

View File

@ -6,7 +6,7 @@ Required:
* Java 11
* Maven 3.5+
To build without executing tests: `mvn clean package -DskipTests`
To build without executing tests: `mvn clean package -Dmaven.test.skip=true`
## Building in IDEA

View File

@ -0,0 +1,66 @@
# hugegraph-commons
[![License](https://img.shields.io/badge/license-Apache%202-0E78BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![codecov](https://codecov.io/gh/hugegraph/hugegraph-common/branch/master/graph/badge.svg)](https://codecov.io/gh/hugegraph/hugegraph-common)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.hugegraph/hugegraph-common/badge.svg)](https://mvnrepository.com/artifact/org.apache.hugegraph/hugegraph-common)
[![CodeQL](https://github.com/apache/incubator-hugegraph-commons/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/apache/incubator-hugegraph-commons/actions/workflows/codeql-analysis.yml)
[![hugegraph-commons ci](https://github.com/apache/incubator-hugegraph-commons/actions/workflows/ci.yml/badge.svg)](https://github.com/apache/incubator-hugegraph-commons/actions/workflows/ci.yml)
hugegraph-commons is a common module for [HugeGraph](https://github.com/apache/hugegraph) and its peripheral components.
hugegraph-commons encapsulates locks, configurations, events, iterators, rest and some
numeric or collection util classes to simplify the development of HugeGraph and its components.
## Components
- Lock: atomic lock, key lock, lock group and lock manger
- Config: register and load config option with security check
- Event: listening and notification, do something asynchronously
- Iterator: some iterators with extra functions, map, filter, extend, etc.
- Rest: RESTful client implemented on OkHttp, POST, PUT, GET and DELETE
- Util: performance analyzer, version checker, numeric and Collection utils, log and exception utils, etc.
- Rpc: rpc component for inner module communication, currently it's based on [Sofa-RPC](https://github.com/sofastack/sofa-rpc)
You could use import the dependencies in `maven` like this:
```xml
<dependency>
<groupId>org.apache.hugegraph</groupId>
<artifactId>hugegraph-common</artifactId>
<version>1.2.0</version>
</dependency>
```
## Learn More
The [doc page](https://hugegraph.apache.org/docs/) contains more information about hugegraph modules.
And here are links of other repositories:
1. [hugegraph-server](https://github.com/apache/hugegraph) (graph's core component - OLTP server)
2. [hugegraph-toolchain](https://github.com/apache/hugegraph-toolchain) (include loader/dashboard/tool/client)
3. [hugegraph-computer](https://github.com/apache/hugegraph-computer) (graph processing system - OLAP)
4. [hugegraph-website/doc](https://github.com/apache/hugegraph-doc) (include doc & website code)
## Contributing
- Welcome to contribute to HugeGraph, please see [How to Contribute](https://hugegraph.apache.org/docs/contribution-guidelines/contribute/) for more information.
- Note: It's recommended to use [GitHub Desktop](https://desktop.github.com/) to greatly simplify the PR and commit process.
- Thank you to all the people who already contributed to HugeGraph!
[![contributors graph](https://contrib.rocks/image?repo=apache/hugegraph-commons)](https://github.com/apache/incubator-hugegraph-commons/graphs/contributors)
## Licence
Same as HugeGraph, hugegraph-commons are also licensed under [Apache 2.0](./LICENSE) License.
### Contact Us
---
- [GitHub Issues](https://github.com/apache/incubator-hugegraph-commons/issues): Feedback on usage issues and functional requirements (quick response)
- Feedback Email: [dev@hugegraph.apache.org](mailto:dev@hugegraph.apache.org) ([subscriber](https://hugegraph.apache.org/docs/contribution-guidelines/subscribe/) only)
- WeChat public account: Apache HugeGraph, welcome to scan this QR code to follow us.
<img src="https://raw.githubusercontent.com/apache/incubator-hugegraph-doc/master/assets/images/wechat.png" alt="QR png" width="350"/>

View File

@ -0,0 +1,23 @@
# hugegraph-common
[![License](https://img.shields.io/badge/license-Apache%202-0E78BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![Build Status](https://travis-ci.org/hugegraph/hugegraph-common.svg?branch=master)](https://travis-ci.org/hugegraph/hugegraph-common)
[![codecov](https://codecov.io/gh/hugegraph/hugegraph-common/branch/master/graph/badge.svg)](https://codecov.io/gh/hugegraph/hugegraph-common)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.hugegraph/hugegraph-common/badge.svg)](https://mvnrepository.com/artifact/org.apache.hugegraph/hugegraph-common)
hugegraph-common is a common module for [HugeGraph](https://github.com/hugegraph/hugegraph) and its peripheral components.
hugegraph-common encapsulates locks, configurations, events, iterators, rest and some
numeric or collection util classes to simplify the development of HugeGraph and
its components.
## Components
- Lock: atomic lock, key lock, lock group and lock manger
- Config: register and load config option with security check
- Event: listening and notification, do something asynchronously
- Iterator: some iterators with extra functions, map, filter, extend, etc.
- Rest: RESTful client implemented on Jersey, POST, PUT, GET and DELETE
- Util: Performance analyzer, version checker, numeric and Collection utils, log and exception utils, etc.
## Licence
The same as HugeGraph, hugegraph-common is also licensed under Apache 2.0 License.

View File

@ -0,0 +1,23 @@
#!/usr/bin/env bash
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
#
export MAVEN_HOME=/home/scmtools/buildkit/maven/apache-maven-3.3.9/
export JAVA_HOME=/home/scmtools/buildkit/java/jdk1.8.0_25/
export PATH=$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH
mvn clean test -Dtest=UnitTestSuite

View File

@ -0,0 +1,302 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.hugegraph</groupId>
<artifactId>hugegraph-commons</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>hugegraph-common</artifactId>
<name>${project.artifactId}</name>
<url>https://github.com/apache/incubator-hugegraph-commons/tree/master/hugegraph-common</url>
<description>
hugegraph-common is a common module for HugeGraph and its peripheral components.
hugegraph-common encapsulates locks, configurations, events, iterators, rest and some
numeric or collection util classes to simplify the development of HugeGraph and its
components.
</description>
<properties>
<!-- Use parent params -->
<lombok.version>1.18.8</lombok.version>
<okhttp.version>4.10.0</okhttp.version>
</properties>
<dependencies>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j2.version}</version>
</dependency>
<!-- Utility -->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<version>${javax.json.version}</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>${commons.configuration.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
<version>${commons.configuration2.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
<exclusion>
<artifactId>commons-lang3</artifactId>
<groupId>org.apache.commons</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>${commons.beanutils.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons.collections.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons.codec.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
<exclusions>
<exclusion>
<artifactId>jsr305</artifactId>
<groupId>com.google.code.findbugs</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>${jsr305.version}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.8</version>
</dependency>
<!-- javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<version>${jackson.version}</version>
<exclusions>
<exclusion>
<artifactId>jakarta.xml.bind-api</artifactId>
<groupId>jakarta.xml.bind</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-base</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${sun.xml.version}</version>
<scope>runtime</scope>
<exclusions>
<exclusion>
<artifactId>jakarta.xml.bind-api</artifactId>
<groupId>jakarta.xml.bind</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-bom</artifactId>
<version>${okhttp.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version>
<executions>
<execution>
<id>pre-unit-test</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>post-unit-test</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<outputDirectory>${project.parent.build.directory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>apache-release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.concurrent;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.hugegraph.util.Log;
import org.slf4j.Logger;
public class AtomicLock {
private static final Logger LOG = Log.logger(LockManager.class);
private String name;
private AtomicReference<Thread> sign;
public AtomicLock(String name) {
this.name = name;
this.sign = new AtomicReference<>();
}
public boolean tryLock() {
Thread current = Thread.currentThread();
return this.sign.compareAndSet(null, current);
}
public void unlock() {
if (this.sign.get() == null) {
return;
}
Thread current = Thread.currentThread();
if (!this.sign.compareAndSet(current, null)) {
throw new RuntimeException(String.format(
"Thread '%s' trying to unlock '%s' " +
"which is held by other threads now.",
current.getName(), this.name));
}
}
public boolean lock(int retries) {
// The interval between retries is exponential growth, most wait
// interval is 2^(retries-1)s. If retries=0, don't retry.
if (retries < 0 || retries > 10) {
throw new IllegalArgumentException(String.format(
"Locking retry times should be in [0, 10], but got %d",
retries));
}
boolean isLocked = false;
try {
for (int i = 0; !(isLocked = this.tryLock()) && i < retries; i++) {
Thread.sleep(1000 * (1L << i));
}
} catch (InterruptedException ignored) {
LOG.info("Thread sleep is interrupted.");
}
return isLocked;
}
public String name() {
return this.name;
}
public void name(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,104 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.concurrent;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.hugegraph.util.E;
public class BarrierEvent {
private final Lock lock = new ReentrantLock();
private final Condition cond = lock.newCondition();
private volatile boolean signaled = false;
/**
* Wait forever until the signal is received.
* @throws InterruptedException if interrupted.
*/
public void await() throws InterruptedException {
this.lock.lock();
try {
while (!this.signaled) {
this.cond.await();
}
} finally {
this.lock.unlock();
}
}
/**
* Wait specified time in milliseconds.
* @param timeout: the time in millisecond to wait.
* @return true if signal is received, false if time out.
* @throws InterruptedException if interrupted.
*/
public boolean await(long timeout) throws InterruptedException {
E.checkArgument(timeout >= 0L,
"The time must be >= 0, but got '%d'.",
timeout);
long deadline = System.currentTimeMillis() + timeout;
this.lock.lock();
try {
while (!this.signaled) {
timeout = deadline - System.currentTimeMillis();
if (timeout > 0) {
this.cond.await(timeout, TimeUnit.MILLISECONDS);
}
if (System.currentTimeMillis() >= deadline) {
return this.signaled;
}
}
} finally {
this.lock.unlock();
}
return true;
}
public void reset() {
this.lock.lock();
try {
this.signaled = false;
} finally {
this.lock.unlock();
}
}
public void signal() {
this.lock.lock();
try {
this.signaled = true;
this.cond.signal();
} finally {
this.lock.unlock();
}
}
public void signalAll() {
this.lock.lock();
try {
this.signaled = true;
this.cond.signalAll();
} finally {
this.lock.unlock();
}
}
}

View File

@ -0,0 +1,144 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.concurrent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.apache.hugegraph.util.E;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Striped;
/**
* KeyLock provide an interface of segment lock
*/
public class KeyLock {
private Striped<Lock> locks;
public KeyLock() {
// The default size is availableProcessors() * 4
this(Runtime.getRuntime().availableProcessors() << 2);
}
public KeyLock(int size) {
this.locks = Striped.lock(size);
}
private int indexOf(Lock lock) {
for (int i = 0; i < this.locks.size(); i++) {
if (this.locks.getAt(i) == lock) {
return i;
}
}
return -1;
}
/**
* Lock an object
* @param key The object to lock
* @return The lock(locked) of passed key
*/
public final Lock lock(Object key) {
E.checkArgument(key != null, "Lock key can't be null");
Lock lock = this.locks.get(key);
lock.lock();
return lock;
}
/**
* Unlock an object
* @param key The object to unlock
*/
public final void unlock(Object key) {
E.checkArgument(key != null, "Unlock key can't be null");
this.locks.get(key).unlock();
}
/**
* Lock a list of object with sorted order
* @param keys The objects to lock
* @return The locks(locked) of keys
*/
public final List<Lock> lockAll(Object... keys) {
E.checkArgument(keys != null && keys.length > 0,
"Lock keys can't be null or empty");
List<Lock> locks = new ArrayList<>(keys.length);
for (Object key : keys) {
E.checkArgument(key != null, "Lock key can't be null");
Lock lock = this.locks.get(key);
locks.add(lock);
}
locks.sort((a, b) -> {
int diff = a.hashCode() - b.hashCode();
if (diff == 0 && a != b) {
diff = this.indexOf(a) - this.indexOf(b);
assert diff != 0;
}
return diff;
});
for (Lock lock : locks) {
lock.lock();
}
return Collections.unmodifiableList(locks);
}
/**
* Lock two objects with sorted order
* NOTE: This is to optimize the performance of lockAll(keys)
* @param key1 The first object
* @param key2 The second object
* @return locks for the two objects
*/
public List<Lock> lockAll(Object key1, Object key2) {
E.checkArgument(key1 != null, "Lock key can't be null");
E.checkArgument(key2 != null, "Lock key can't be null");
Lock lock1 = this.locks.get(key1);
Lock lock2 = this.locks.get(key2);
int diff = lock1.hashCode() - lock2.hashCode();
if (diff == 0 && lock1 != lock2) {
diff = this.indexOf(lock1) - this.indexOf(lock2);
assert diff != 0;
}
List<Lock> locks = diff > 0 ?
ImmutableList.of(lock2, lock1) :
ImmutableList.of(lock1, lock2);
for (Lock lock : locks) {
lock.lock();
}
return locks;
}
/**
* Unlock a list of object
* @param locks The locks to unlock
*/
public final void unlockAll(List<Lock> locks) {
E.checkArgument(locks != null, "Unlock locks can't be null");
for (int i = locks.size(); i > 0; i--) {
assert this.indexOf(locks.get(i - 1)) != -1;
locks.get(i - 1).unlock();
}
}
}

View File

@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.concurrent;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LockGroup {
private final String name;
private final Map<String, Object> locksMap;
public LockGroup(String lockGroup) {
this.name = lockGroup;
this.locksMap = new ConcurrentHashMap<>();
}
public Lock lock(String lockName) {
if (!this.locksMap.containsKey(lockName)) {
this.locksMap.putIfAbsent(lockName, new ReentrantLock());
}
return (Lock) this.locksMap.get(lockName);
}
public AtomicLock atomicLock(String lockName) {
if (!this.locksMap.containsKey(lockName)) {
this.locksMap.putIfAbsent(lockName, new AtomicLock(lockName));
}
return (AtomicLock) this.locksMap.get(lockName);
}
public ReadWriteLock readWriteLock(String lockName) {
if (!this.locksMap.containsKey(lockName)) {
this.locksMap.putIfAbsent(lockName, new ReentrantReadWriteLock());
}
return (ReadWriteLock) this.locksMap.get(lockName);
}
public KeyLock keyLock(String lockName) {
if (!this.locksMap.containsKey(lockName)) {
this.locksMap.putIfAbsent(lockName, new KeyLock());
}
return (KeyLock) this.locksMap.get(lockName);
}
public KeyLock keyLock(String lockName, int size) {
if (!this.locksMap.containsKey(lockName)) {
this.locksMap.putIfAbsent(lockName, new KeyLock(size));
}
return (KeyLock) this.locksMap.get(lockName);
}
public <K extends Comparable<K>> RowLock<K> rowLock(String lockName) {
if (!this.locksMap.containsKey(lockName)) {
this.locksMap.putIfAbsent(lockName, new RowLock<>());
}
Object value = this.locksMap.get(lockName);
@SuppressWarnings("unchecked")
RowLock<K> lock = (RowLock<K>) value;
return lock;
}
public String name() {
return this.name;
}
}

View File

@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.concurrent;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class LockManager {
private static final LockManager INSTANCE = new LockManager();
public static LockManager instance() {
return INSTANCE;
}
private Map<String, LockGroup> lockGroupMap;
private LockManager() {
this.lockGroupMap = new ConcurrentHashMap<>();
}
public boolean exists(String group) {
return this.lockGroupMap.containsKey(group);
}
public LockGroup create(String group) {
if (exists(group)) {
throw new RuntimeException(String.format(
"LockGroup '%s' already exists", group));
}
LockGroup lockGroup = new LockGroup(group);
LockGroup previous = this.lockGroupMap.putIfAbsent(group, lockGroup);
if (previous != null) {
return previous;
}
return lockGroup;
}
public LockGroup get(String group) {
LockGroup lockGroup = this.lockGroupMap.get(group);
if (lockGroup == null) {
throw new RuntimeException(String.format(
"LockGroup '%s' does not exists", group));
}
return lockGroup;
}
public void destroy(String group) {
if (this.exists(group)) {
this.lockGroupMap.remove(group);
} else {
throw new RuntimeException(String.format(
"LockGroup '%s' does not exists", group));
}
}
}

View File

@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.concurrent;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import org.apache.hugegraph.util.Log;
import org.slf4j.Logger;
public class PausableScheduledThreadPool extends ScheduledThreadPoolExecutor {
private static final Logger LOG = Log.logger(PausableScheduledThreadPool.class);
private volatile boolean paused = false;
public PausableScheduledThreadPool(int corePoolSize,
ThreadFactory factory) {
super(corePoolSize, factory);
}
public synchronized void pauseSchedule() {
this.paused = true;
LOG.info("PausableScheduledThreadPool was paused");
}
public synchronized void resumeSchedule() {
this.paused = false;
this.notifyAll();
LOG.info("PausableScheduledThreadPool was resumed");
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
synchronized (this) {
while (this.paused) {
try {
this.wait();
} catch (InterruptedException e) {
LOG.warn("PausableScheduledThreadPool was interrupted");
}
}
}
super.beforeExecute(t, r);
}
@Override
public void shutdown() {
if (this.paused) {
this.resumeSchedule();
}
super.shutdown();
}
@Override
public List<Runnable> shutdownNow() {
if (this.paused) {
this.resumeSchedule();
}
return super.shutdownNow();
}
}

View File

@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.concurrent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.hugegraph.util.E;
public class RowLock<K extends Comparable<K>> {
private final Map<K, Lock> locks = new ConcurrentHashMap<>();
private final ThreadLocal<Map<K, LocalLock>> localLocks =
ThreadLocal.withInitial(HashMap::new);
public void lock(K key) {
E.checkArgument(key != null, "Lock key can't be null");
LocalLock localLock = this.localLocks.get().get(key);
if (localLock != null) {
localLock.lockCount++;
} else {
Lock current = new ReentrantLock();
Lock previous = this.locks.putIfAbsent(key, current);
if (previous != null) {
current = previous;
}
current.lock();
this.localLocks.get().put(key, new LocalLock(current));
}
}
public void unlock(K key) {
E.checkArgument(key != null, "Unlock key can't be null");
LocalLock localLock = this.localLocks.get().get(key);
if (localLock == null) {
return;
}
if (--localLock.lockCount == 0) {
this.locks.remove(key, localLock.current);
this.localLocks.get().remove(key);
localLock.current.unlock();
}
E.checkState(localLock.lockCount >= 0,
"The lock count must be >= 0, but got %s",
localLock.lockCount);
}
public void lockAll(Set<K> keys) {
E.checkArgument(keys != null && keys.size() > 0,
"Lock keys can't be null or empty");
List<K> list = new ArrayList<>(keys);
Collections.sort(list);
for (K key : list) {
this.lock(key);
}
}
public void unlockAll(Set<K> keys) {
E.checkArgument(keys != null && keys.size() > 0,
"Unlock keys can't be null or empty");
for (K key : keys) {
this.unlock(key);
}
}
private static class LocalLock {
private final Lock current;
private int lockCount;
private LocalLock(Lock current) {
this.current = current;
this.lockCount = 1;
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.config;
import java.util.function.Function;
import org.apache.hugegraph.util.E;
import com.google.common.base.Predicate;
public class ConfigConvOption<T, R> extends TypedOption<T, R> {
private final Function<T, R> converter;
public ConfigConvOption(String name, String desc, Predicate<T> pred,
Function<T, R> convert, T value) {
this(name, false, desc, pred, convert, value);
}
@SuppressWarnings("unchecked")
public ConfigConvOption(String name, boolean required, String desc,
Predicate<T> pred, Function<T, R> convert,
T value) {
super(name, required, desc, pred, (Class<T>) value.getClass(), value);
E.checkNotNull(convert, "convert");
this.converter = convert;
}
@Override
public R convert(T value) {
return this.converter.apply(value);
}
}

View File

@ -0,0 +1,39 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.config;
public class ConfigException extends RuntimeException {
private static final long serialVersionUID = -8711375282196157058L;
public ConfigException(String message) {
super(message);
}
public ConfigException(String message, Throwable cause) {
super(message, cause);
}
public ConfigException(String message, Object... args) {
super(String.format(message, args));
}
public ConfigException(String message, Throwable cause, Object... args) {
super(String.format(message, args), cause);
}
}

View File

@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import org.apache.hugegraph.util.E;
import com.google.common.base.Predicate;
public class ConfigListConvOption<T, R> extends TypedOption<List<T>, List<R>> {
private final Class<T> elemClass;
private final Function<T, R> converter;
@SuppressWarnings("unchecked")
public ConfigListConvOption(String name, String desc,
Predicate<List<T>> pred, Function<T, R> convert,
T... values) {
this(name, false, desc, pred, convert, null, Arrays.asList(values));
}
@SuppressWarnings("unchecked")
public ConfigListConvOption(String name, boolean required, String desc,
Predicate<List<T>> pred, Function<T, R> convert,
Class<T> clazz, List<T> values) {
super(name, required, desc, pred,
(Class<List<T>>) values.getClass(), values);
E.checkNotNull(convert, "convert");
if (clazz == null && values.size() > 0) {
clazz = (Class<T>) values.get(0).getClass();
}
E.checkArgumentNotNull(clazz, "Element class can't be null");
this.elemClass = clazz;
this.converter = convert;
}
@Override
protected boolean forList() {
return true;
}
@Override
protected List<T> parse(String value) {
return ConfigListOption.convert(value, part -> {
return this.parse(part, this.elemClass);
});
}
@Override
public List<R> convert(List<T> values) {
List<R> results = new ArrayList<>(values.size());
for (T value : values) {
results.add(this.converter.apply(value));
}
return results;
}
}

View File

@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import org.apache.hugegraph.util.E;
import com.google.common.base.Predicate;
public class ConfigListOption<T> extends ConfigOption<List<T>> {
private final Class<T> elemClass;
@SuppressWarnings("unchecked")
public ConfigListOption(String name, String desc,
Predicate<List<T>> pred, T... values) {
this(name, false, desc, pred, null, Arrays.asList(values));
}
@SuppressWarnings("unchecked")
public ConfigListOption(String name, boolean required, String desc,
Predicate<List<T>> pred, Class<T> clazz,
List<T> values) {
super(name, required, desc, pred,
(Class<List<T>>) values.getClass(), values);
if (clazz == null && values.size() > 0) {
clazz = (Class<T>) values.get(0).getClass();
}
E.checkArgumentNotNull(clazz, "Element class can't be null");
this.elemClass = clazz;
}
@Override
protected boolean forList() {
return true;
}
@Override
protected List<T> parse(String value) {
return convert(value, part -> this.parse(part, this.elemClass));
}
@SuppressWarnings("unchecked")
public static <T> List<T> convert(Object value, Function<String, ?> conv) {
if (value instanceof List) {
return (List<T>) value;
}
// If target data type is List, parse it as a list
String str = (String) value;
if (str.startsWith("[") && str.endsWith("]")) {
str = str.substring(1, str.length() - 1);
}
String[] parts = str.split(",");
List<T> results = new ArrayList<>(parts.length);
for (String part : parts) {
results.add((T) conv.apply(part.trim()));
}
return results;
}
}

View File

@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.config;
import com.google.common.base.Predicate;
public class ConfigOption<T> extends TypedOption<T, T> {
public ConfigOption(String name, String desc, T value) {
this(name, desc, null, value);
}
@SuppressWarnings("unchecked")
public ConfigOption(String name, String desc, Predicate<T> pred, T value) {
this(name, false, desc, pred, (Class<T>) value.getClass(), value);
}
public ConfigOption(String name, boolean required, String desc,
Predicate<T> pred, Class<T> type, T value) {
super(name, required, desc, pred, type, value);
}
}

View File

@ -0,0 +1,216 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.config;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.FileBasedConfiguration;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.YAMLConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.io.FileHandler;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.Log;
import org.slf4j.Logger;
public class HugeConfig extends PropertiesConfiguration {
private static final Logger LOG = Log.logger(HugeConfig.class);
private String configPath;
public HugeConfig(Configuration config) {
loadConfig(config);
this.configPath = null;
}
public HugeConfig(String configFile) {
loadConfig(loadConfigFile(configFile));
this.configPath = configFile;
}
public HugeConfig(Map<String, Object> propertyMap) {
if (propertyMap == null) {
throw new ConfigException("The property map is null");
}
for (Map.Entry<String, Object> kv : propertyMap.entrySet()) {
this.addProperty(kv.getKey(), kv.getValue());
}
this.checkRequiredOptions();
}
private void loadConfig(Configuration config) {
if (config == null) {
throw new ConfigException("The config object is null");
}
this.setLayoutIfNeeded(config);
this.append(config);
this.checkRequiredOptions();
}
private void setLayoutIfNeeded(Configuration conf) {
if (!(conf instanceof PropertiesConfiguration)) {
return;
}
PropertiesConfiguration propConf = (PropertiesConfiguration) conf;
this.setLayout(propConf.getLayout());
}
@SuppressWarnings("unchecked")
public <T, R> R get(TypedOption<T, R> option) {
Object value = this.getProperty(option.name());
if (value == null) {
return option.defaultValue();
}
return (R) value;
}
public Map<String, String> getMap(ConfigListOption<String> option) {
List<String> values = this.get(option);
Map<String, String> result = new HashMap<>();
for (String value : values) {
String[] pair = value.split(":", 2);
E.checkState(pair.length == 2,
"Invalid option format for '%s': %s(expect KEY:VALUE)",
option.name(), value);
result.put(pair[0].trim(), pair[1].trim());
}
return result;
}
@Override
public void addPropertyDirect(String key, Object value) {
TypedOption<?, ?> option = OptionSpace.get(key);
if (option == null) {
LOG.warn("The config option '{}' is redundant, " +
"please ensure it has been registered", key);
} else {
// The input value is String(parsed by PropertiesConfiguration)
value = this.validateOption(key, value);
}
if (this.containsKey(key) && value instanceof List) {
for (Object item : (List<?>) value) {
super.addPropertyDirect(key, item);
}
} else {
super.addPropertyDirect(key, value);
}
}
@Override
protected void addPropertyInternal(String key, Object value) {
this.addPropertyDirect(key, value);
}
private Object validateOption(String key, Object value) {
TypedOption<?, ?> option = OptionSpace.get(key);
if (value instanceof String) {
return option.parseConvert((String) value);
}
Class<?> dataType = option.dataType();
if (dataType.isInstance(value)) {
return value;
}
throw new IllegalArgumentException(
String.format("Invalid value for key '%s': '%s'", key, value));
}
private void checkRequiredOptions() {
// TODO: Check required options must be contained in this map
}
public void save(File copiedFile) throws ConfigurationException {
FileHandler fileHandler = new FileHandler(this);
fileHandler.save(copiedFile);
}
@Nullable
public File file() {
if (StringUtils.isEmpty(this.configPath)) {
return null;
}
return new File(this.configPath);
}
public void file(String path) {
this.configPath = path;
}
private static Configuration loadConfigFile(String path) {
E.checkNotNull(path, "config path");
E.checkArgument(!path.isEmpty(),
"The config path can't be empty");
File file = new File(path);
return loadConfigFile(file);
}
private static Configuration loadConfigFile(File configFile) {
E.checkArgument(configFile.exists() &&
configFile.isFile() &&
configFile.canRead(),
"Please specify a proper config file rather than: '%s'",
configFile.toString());
try {
String fileName = configFile.getName();
String fileExtension = FilenameUtils.getExtension(fileName);
Configuration config;
Configurations configs = new Configurations();
switch (fileExtension) {
case "yml":
case "yaml":
Parameters params = new Parameters();
FileBasedConfigurationBuilder<FileBasedConfiguration> builder =
new FileBasedConfigurationBuilder(YAMLConfiguration.class)
.configure(params.fileBased().setFile(configFile));
config = builder.getConfiguration();
break;
case "xml":
config = configs.xml(configFile);
break;
default:
config = configs.properties(configFile);
break;
}
return config;
} catch (ConfigurationException e) {
throw new ConfigException("Unable to load config: '%s'",
e, configFile);
}
}
}

View File

@ -0,0 +1,83 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.config;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate;
public final class OptionChecker {
public static <O> Predicate<O> disallowEmpty() {
return o -> {
if (o == null) {
return false;
}
if (o instanceof String) {
return StringUtils.isNotBlank((String) o);
}
if (o.getClass().isArray() && (Array.getLength(o) == 0)) {
return false;
}
return !(o instanceof Iterable) || ((Iterable<?>) o).iterator().hasNext();
};
}
@SuppressWarnings("unchecked")
public static <O> Predicate<O> allowValues(O... values) {
return o -> o != null && Arrays.asList(values).contains(o);
}
@SuppressWarnings("unchecked")
public static <O> Predicate<List<O>> inValues(O... values) {
return o -> o != null && new HashSet<>(Arrays.asList(values)).containsAll(o);
}
public static <N extends Number> Predicate<N> positiveInt() {
return number -> number != null && number.longValue() > 0;
}
public static <N extends Number> Predicate<N> nonNegativeInt() {
return number -> number != null && number.longValue() >= 0;
}
public static <N extends Number> Predicate<N> rangeInt(N min, N max) {
return number -> {
if (number == null) {
return false;
}
long value = number.longValue();
return value >= min.longValue() && value <= max.longValue();
};
}
public static <N extends Number> Predicate<N> rangeDouble(N min, N max) {
return number -> {
if (number == null) {
return false;
}
double value = number.doubleValue();
return value >= min.doubleValue() && value <= max.doubleValue();
};
}
}

View File

@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.config;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.hugegraph.util.Log;
import org.slf4j.Logger;
public class OptionHolder {
private static final Logger LOG = Log.logger(HugeConfig.class);
protected Map<String, TypedOption<?, ?>> options;
public OptionHolder() {
this.options = new HashMap<>();
}
protected void registerOptions() {
for (Field field : this.getClass().getFields()) {
if (!TypedOption.class.isAssignableFrom(field.getType())) {
// Skip if not option
continue;
}
try {
TypedOption<?, ?> option = (TypedOption<?, ?>) field.get(this);
// Fields of subclass first, don't overwrite by superclass
this.options.putIfAbsent(option.name(), option);
} catch (Exception e) {
LOG.error("Failed to register option: {}", field, e);
throw new ConfigException(
"Failed to register option: %s", field);
}
}
}
public Map<String, TypedOption<?, ?>> options() {
return Collections.unmodifiableMap(this.options);
}
}

View File

@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.config;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.Log;
import org.slf4j.Logger;
public final class OptionSpace {
private static final Logger LOG = Log.logger(OptionSpace.class);
private static final Map<String, Class<? extends OptionHolder>> HOLDERS;
private static final Map<String, TypedOption<?, ?>> OPTIONS;
private static final String INSTANCE_METHOD = "instance";
static {
HOLDERS = new ConcurrentHashMap<>();
OPTIONS = new ConcurrentHashMap<>();
}
public static void register(String module, String holder) {
ClassLoader classLoader = OptionSpace.class.getClassLoader();
Class<?> clazz;
try {
clazz = classLoader.loadClass(holder);
} catch (ClassNotFoundException e) {
throw new ConfigException(
"Failed to load class of option holder '%s'", e, holder);
}
// Check subclass
if (!OptionHolder.class.isAssignableFrom(clazz)) {
throw new ConfigException(
"Class '%s' is not a subclass of OptionHolder", holder);
}
OptionHolder instance = null;
Exception exception = null;
try {
Method method = clazz.getMethod(INSTANCE_METHOD);
if (!Modifier.isStatic(method.getModifiers())) {
throw new NoSuchMethodException(INSTANCE_METHOD);
}
instance = (OptionHolder) method.invoke(null);
if (instance == null) {
exception = new ConfigException(
"Returned null from %s() method",
INSTANCE_METHOD);
}
} catch (NoSuchMethodException e) {
LOG.warn("Class {} does not has static method {}.",
holder, INSTANCE_METHOD);
exception = e;
} catch (InvocationTargetException e) {
LOG.warn("Can't call static method {} from class {}.",
INSTANCE_METHOD, holder);
exception = e;
} catch (IllegalAccessException e) {
LOG.warn("Illegal access while calling method {} from class {}.",
INSTANCE_METHOD, holder);
exception = e;
}
if (exception != null) {
throw new ConfigException("Failed to instantiate option holder: %s",
exception, holder);
}
register(module, instance);
}
public static void register(String module, OptionHolder holder) {
// Check exists
if (HOLDERS.containsKey(module)) {
LOG.warn("Already registered option holder: {} ({})",
module, HOLDERS.get(module));
}
E.checkArgumentNotNull(holder, "OptionHolder can't be null");
HOLDERS.put(module, holder.getClass());
OPTIONS.putAll(holder.options());
LOG.debug("Registered options for OptionHolder: {}",
holder.getClass().getSimpleName());
}
public static Set<String> keys() {
return Collections.unmodifiableSet(OPTIONS.keySet());
}
public static boolean containKey(String key) {
return OPTIONS.containsKey(key);
}
public static TypedOption<?, ?> get(String key) {
return OPTIONS.get(key);
}
}

View File

@ -0,0 +1,194 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.config;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.Log;
import org.apache.commons.configuration.PropertyConverter;
import org.slf4j.Logger;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
public class TypedOption<T, R> {
private static final Logger LOG = Log.logger(TypedOption.class);
private static final Set<Class<?>> ACCEPTED_DATA_TYPES;
private static final String ACCEPTED_DATA_TYPES_STRING;
static {
ACCEPTED_DATA_TYPES = ImmutableSet.of(
Boolean.class,
Short.class,
Integer.class,
Byte.class,
Long.class,
Float.class,
Double.class,
String.class,
String[].class,
Class.class,
List.class
);
ACCEPTED_DATA_TYPES_STRING = Joiner.on(", ").join(ACCEPTED_DATA_TYPES);
}
private final String name;
private final String desc;
private final boolean required;
private final Class<T> dataType;
private final T defaultValue;
private final Predicate<T> checkFunc;
@SuppressWarnings("unchecked")
public TypedOption(String name, boolean required, String desc,
Predicate<T> pred, Class<T> type, T value) {
E.checkNotNull(name, "name");
E.checkNotNull(type, "dataType");
this.name = name;
this.dataType = (Class<T>) this.checkAndAssignDataType(type);
this.defaultValue = value;
this.required = required;
this.desc = desc;
this.checkFunc = pred;
this.check(this.defaultValue);
}
private Class<?> checkAndAssignDataType(Class<T> dataType) {
for (Class<?> clazz : ACCEPTED_DATA_TYPES) {
if (clazz.isAssignableFrom(dataType)) {
return clazz;
}
}
String msg = String.format("Input data type '%s' doesn't belong " +
"to acceptable type set: [%s]",
dataType, ACCEPTED_DATA_TYPES_STRING);
throw new IllegalArgumentException(msg);
}
public String name() {
return this.name;
}
public Class<T> dataType() {
return this.dataType;
}
public String desc() {
return this.desc;
}
public boolean required() {
return this.required;
}
public R defaultValue() {
return this.convert(this.defaultValue);
}
public R parseConvert(String value) {
T parsed = this.parse(value);
this.check(parsed);
return this.convert(parsed);
}
@SuppressWarnings("unchecked")
protected T parse(String value) {
return (T) this.parse(value, this.dataType);
}
protected Object parse(String value, Class<?> dataType) {
if (dataType.equals(String.class)) {
return value;
} else if (dataType.equals(Class.class)) {
try {
if (value.startsWith("class")) {
value = value.substring("class".length()).trim();
}
return Class.forName(value);
} catch (ClassNotFoundException e) {
throw new ConfigException(
"Failed to parse Class from String '%s'", e, value);
}
} else if (List.class.isAssignableFrom(dataType)) {
E.checkState(this.forList(),
"List option can't be registered with class %s",
this.getClass().getSimpleName());
}
// Use PropertyConverter method `toXXX` convert value
String methodTo = "to" + dataType.getSimpleName();
try {
Method method = PropertyConverter.class.getMethod(
methodTo, Object.class);
return method.invoke(null, value);
} catch (ReflectiveOperationException e) {
LOG.error("Invalid type of value '{}' for option '{}'",
value, this.name, e);
throw new ConfigException(
"Invalid type of value '%s' for option '%s', " +
"expect '%s' type",
value, this.name, dataType.getSimpleName());
}
}
protected void check(Object value) {
E.checkNotNull(value, "value", this.name);
if (!this.dataType.isInstance(value)) {
throw new ConfigException(
"Invalid type of value '%s' for option '%s', " +
"expect type %s but got %s", value, this.name,
this.dataType.getSimpleName(),
value.getClass().getSimpleName());
}
if (this.checkFunc != null) {
@SuppressWarnings("unchecked")
T result = (T) value;
if (!this.checkFunc.apply(result)) {
throw new ConfigException("Invalid option value for '%s': %s",
this.name, value);
}
}
}
@SuppressWarnings("unchecked")
protected R convert(T value) {
return (R) value;
}
protected boolean forList() {
return false;
}
@Override
public String toString() {
return String.format("[%s]%s=%s", this.dataType.getSimpleName(),
this.name, this.defaultValue);
}
}

View File

@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.date;
import java.util.Date;
import java.util.TimeZone;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
/**
* The SafeDateFormat actually is a proxy for joda DateTimeFormatter
*/
public class SafeDateFormat {
private static final int ONE_HOUR_MS = 3600 * 1000;
private final String pattern;
private DateTimeFormatter formatter;
public SafeDateFormat(String pattern) {
this.pattern = pattern;
this.formatter = DateTimeFormat.forPattern(pattern);
}
public synchronized void setTimeZone(String zoneId) {
int hoursOffset = TimeZone.getTimeZone(zoneId).getRawOffset() /
ONE_HOUR_MS;
DateTimeZone zone = DateTimeZone.forOffsetHours(hoursOffset);
this.formatter = this.formatter.withZone(zone);
}
public TimeZone getTimeZone() {
return this.formatter.getZone().toTimeZone();
}
public Date parse(String source) {
return this.formatter.parseDateTime(source).toDate();
}
public String format(Date date) {
return this.formatter.print(date.getTime());
}
public Object toPattern() {
return this.pattern;
}
}

View File

@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.event;
import java.util.Arrays;
import java.util.Collections;
import org.apache.hugegraph.util.E;
public class Event extends java.util.EventObject {
private static final long serialVersionUID = 1625973849208342813L;
private String name;
private Object[] args;
public Event(Object source, String event) {
this(source, event, Collections.emptyList().toArray());
}
public Event(Object source, String event, Object... args) {
super(source);
this.name = event;
this.args = args;
}
public String name() {
return this.name;
}
public Object[] args() {
return this.args;
}
public void checkArgs(Class<?>... classes) throws IllegalArgumentException {
E.checkArgument(this.args.length == classes.length,
"The args count of event '%s' should be %s(actual %s)",
this.name, classes.length, this.args.length);
int i = 0;
for (Class<?> c : classes) {
Object arg = this.args[i++];
if (arg == null) {
continue;
}
E.checkArgument(c.isAssignableFrom(arg.getClass()),
"The arg '%s'(%s) can't match %s",
arg, arg.getClass(), c);
}
}
@Override
public String toString() {
return String.format("Event{name='%s', args=%s}",
this.name, Arrays.asList(this.args));
}
}

View File

@ -0,0 +1,197 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.event;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.ExecutorUtil;
import org.apache.hugegraph.util.Log;
import org.slf4j.Logger;
import org.apache.hugegraph.iterator.ExtendableIterator;
import com.google.common.collect.ImmutableList;
public class EventHub {
private static final Logger LOG = Log.logger(EventHub.class);
public static final String EVENT_WORKER = "event-worker-%d";
public static final String ANY_EVENT = "*";
private static final List<EventListener> EMPTY = ImmutableList.of();
// Event executor
private static ExecutorService executor = null;
private String name;
private Map<String, List<EventListener>> listeners;
public EventHub() {
this("hub");
}
public EventHub(String name) {
this(name, 1, Runtime.getRuntime().availableProcessors() << 2);
}
public EventHub(String name, int threadSize) {
LOG.debug("Create new EventHub {},threadSize {}", name, threadSize);
this.name = name;
this.listeners = new ConcurrentHashMap<>();
EventHub.init(threadSize);
}
public EventHub(String name, int corePoolSize, int maximumPoolSize) {
LOG.debug("Create new EventHub {},corePoolSize {}, maximumPoolSize {}", name, corePoolSize, maximumPoolSize);
this.name = name;
this.listeners = new ConcurrentHashMap<>();
EventHub.init(corePoolSize, maximumPoolSize);
}
public static synchronized void init(int poolSize) {
if (executor != null) {
return;
}
LOG.debug("Init pool(size {}) for EventHub", poolSize);
executor = ExecutorUtil.newFixedThreadPool(poolSize, EVENT_WORKER);
}
public static synchronized void init(int corePoolSize, int maximumPoolSize) {
LOG.debug("Init corePoolSize {}, maximumPoolSize {} for EventHub", corePoolSize, maximumPoolSize);
if (executor != null) {
LOG.debug("EventHub executor already initialized");
return;
}
executor = ExecutorUtil.newDynamicThreadExecutor(EVENT_WORKER, corePoolSize, maximumPoolSize);
}
public static synchronized boolean destroy(long timeout)
throws InterruptedException {
E.checkState(executor != null, "EventHub has not been initialized");
LOG.debug("Destroy pool for EventHub");
executor.shutdown();
return executor.awaitTermination(timeout, TimeUnit.SECONDS);
}
private static ExecutorService executor() {
ExecutorService e = executor;
E.checkState(e != null, "The event executor has been destroyed");
return e;
}
public String name() {
return this.name;
}
public boolean containsListener(String event) {
List<EventListener> ls = this.listeners.get(event);
return ls != null && ls.size() > 0;
}
public List<EventListener> listeners(String event) {
List<EventListener> ls = this.listeners.get(event);
return ls == null ? EMPTY : Collections.unmodifiableList(ls);
}
public void listen(String event, EventListener listener) {
E.checkNotNull(event, "event");
E.checkNotNull(listener, "event listener");
if (!this.listeners.containsKey(event)) {
this.listeners.putIfAbsent(event, new CopyOnWriteArrayList<>());
}
List<EventListener> ls = this.listeners.get(event);
assert ls != null : this.listeners;
ls.add(listener);
}
public List<EventListener> unlisten(String event) {
List<EventListener> ls = this.listeners.remove(event);
return ls == null ? EMPTY : Collections.unmodifiableList(ls);
}
public int unlisten(String event, EventListener listener) {
List<EventListener> ls = this.listeners.get(event);
if (ls == null) {
return 0;
}
int count = 0;
while (ls.remove(listener)) {
count++;
}
return count;
}
public Future<Integer> notify(String event, @Nullable Object... args) {
@SuppressWarnings("resource")
ExtendableIterator<EventListener> all = new ExtendableIterator<>();
List<EventListener> ls = this.listeners.get(event);
if (ls != null && !ls.isEmpty()) {
all.extend(ls.iterator());
}
List<EventListener> lsAny = this.listeners.get(ANY_EVENT);
if (lsAny != null && !lsAny.isEmpty()) {
all.extend(lsAny.iterator());
}
if (!all.hasNext()) {
return CompletableFuture.completedFuture(0);
}
Event ev = new Event(this, event, args);
// The submit will catch params: `all`(Listeners) and `ev`(Event)
return executor().submit(() -> {
int count = 0;
// Notify all listeners, and ignore the results
while (all.hasNext()) {
try {
all.next().event(ev);
count++;
} catch (Throwable e) {
LOG.warn("Failed to handle event: {}", ev, e);
}
}
return count;
});
}
public Object call(String event, @Nullable Object... args) {
List<EventListener> ls = this.listeners.get(event);
if (ls == null) {
throw new RuntimeException("Not found listener for: " + event);
} else if (ls.size() != 1) {
throw new RuntimeException("Too many listeners for: " + event);
}
EventListener listener = ls.get(0);
return listener.event(new Event(this, event, args));
}
}

View File

@ -0,0 +1,27 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.event;
public interface EventListener extends java.util.EventListener {
/**
* The event callback
* @param event object
* @return event result
*/
Object event(Event event);
}

View File

@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.func;
public interface TriFunction<T1, T2, T3, R> {
R apply(T1 v1, T2 v2, T3 v3);
}

View File

@ -0,0 +1,104 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.iterator;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.InsertionOrderUtil;
import com.google.common.collect.ImmutableList;
public class BatchMapperIterator<T, R> extends WrappedIterator<R> {
private final int batch;
private final Iterator<T> originIterator;
private final Function<List<T>, Iterator<R>> mapperCallback;
private Iterator<R> batchIterator;
public BatchMapperIterator(int batch, Iterator<T> origin,
Function<List<T>, Iterator<R>> mapper) {
E.checkArgument(batch > 0, "Expect batch > 0, but got %s", batch);
this.batch = batch;
this.originIterator = origin;
this.mapperCallback = mapper;
this.batchIterator = null;
}
@Override
protected Iterator<T> originIterator() {
return this.originIterator;
}
@Override
protected final boolean fetch() {
if (this.batchIterator != null && this.fetchFromBatch()) {
return true;
}
List<T> batch = this.nextBatch();
assert this.batchIterator == null;
while (!batch.isEmpty()) {
// Do fetch
this.batchIterator = this.mapperCallback.apply(batch);
if (this.batchIterator != null && this.fetchFromBatch()) {
return true;
}
// Try next batch
batch = this.nextBatch();
}
return false;
}
protected final List<T> nextBatch() {
if (!this.originIterator.hasNext()) {
return ImmutableList.of();
}
List<T> list = InsertionOrderUtil.newList();
for (int i = 0; i < this.batch && this.originIterator.hasNext(); i++) {
T next = this.originIterator.next();
list.add(next);
}
return list;
}
protected final boolean fetchFromBatch() {
E.checkNotNull(this.batchIterator, "mapper results");
while (this.batchIterator.hasNext()) {
R result = this.batchIterator.next();
if (result != null) {
assert this.current == none();
this.current = result;
return true;
}
}
this.resetBatchIterator();
return false;
}
protected final void resetBatchIterator() {
if (this.batchIterator == null) {
return;
}
close(this.batchIterator);
this.batchIterator = null;
}
}

View File

@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.iterator;
import java.util.Iterator;
public interface CIter<R> extends Iterator<R>, AutoCloseable, Metadatable {
}

View File

@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.iterator;
import java.util.Deque;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedDeque;
import org.apache.hugegraph.util.E;
public class ExtendableIterator<T> extends WrappedIterator<T> {
private final Deque<Iterator<T>> itors;
private Iterator<T> currentIterator;
public ExtendableIterator() {
this.itors = new ConcurrentLinkedDeque<>();
this.currentIterator = null;
}
public ExtendableIterator(Iterator<T> iter) {
this();
this.extend(iter);
}
public ExtendableIterator(Iterator<T> itor1, Iterator<T> itor2) {
this();
this.extend(itor1);
this.extend(itor2);
}
public ExtendableIterator<T> extend(Iterator<T> iter) {
E.checkState(this.currentIterator == null,
"Can't extend iterator after iterating");
if (iter != null) {
this.itors.addLast(iter);
}
return this;
}
@Override
public void close() throws Exception {
for (Iterator<T> iter : this.itors) {
if (iter instanceof AutoCloseable) {
((AutoCloseable) iter).close();
}
}
}
@Override
protected Iterator<T> originIterator() {
return this.currentIterator;
}
@Override
protected boolean fetch() {
assert this.current == none();
if (this.itors.isEmpty()) {
return false;
}
if (this.currentIterator != null && this.currentIterator.hasNext()) {
this.current = this.currentIterator.next();
return true;
}
Iterator<T> first;
while ((first = this.itors.peekFirst()) != null && !first.hasNext()) {
if (first == this.itors.peekLast() && this.itors.size() == 1) {
this.currentIterator = first;
// The last one
return false;
}
close(this.itors.removeFirst());
}
assert first != null && first.hasNext();
this.currentIterator = first;
this.current = this.currentIterator.next();
return true;
}
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.iterator;
import java.util.Iterator;
import java.util.function.Function;
public class FilterIterator<T> extends WrappedIterator<T> {
private final Iterator<T> originIterator;
private final Function<T, Boolean> filterCallback;
public FilterIterator(Iterator<T> origin, Function<T, Boolean> filter) {
this.originIterator = origin;
this.filterCallback = filter;
}
@Override
protected Iterator<T> originIterator() {
return this.originIterator;
}
@Override
protected final boolean fetch() {
while (this.originIterator.hasNext()) {
T next = this.originIterator.next();
// Do filter
if (next != null && this.filterCallback.apply(next)) {
assert this.current == none();
this.current = next;
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.iterator;
import java.util.Iterator;
import java.util.function.Function;
import org.apache.hugegraph.util.E;
public class FlatMapperFilterIterator<T, R> extends FlatMapperIterator<T, R> {
private final Function<R, Boolean> filterCallback;
public FlatMapperFilterIterator(Iterator<T> origin,
Function<T, Iterator<R>> mapper,
Function<R, Boolean> filter) {
super(origin, mapper);
this.filterCallback = filter;
}
@Override
protected final boolean fetchFromBatch() {
E.checkNotNull(this.batchIterator, "mapper results");
while (this.batchIterator.hasNext()) {
R result = this.batchIterator.next();
if (result != null && this.filterCallback.apply(result)) {
assert this.current == none();
this.current = result;
return true;
}
}
this.resetBatchIterator();
return false;
}
}

View File

@ -0,0 +1,89 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.iterator;
import java.util.Iterator;
import java.util.function.Function;
import org.apache.hugegraph.util.E;
public class FlatMapperIterator<T, R> extends WrappedIterator<R> {
private final Iterator<T> originIterator;
private final Function<T, Iterator<R>> mapperCallback;
protected Iterator<R> batchIterator;
public FlatMapperIterator(Iterator<T> origin,
Function<T, Iterator<R>> mapper) {
this.originIterator = origin;
this.mapperCallback = mapper;
this.batchIterator = null;
}
@Override
public void close() throws Exception {
this.resetBatchIterator();
super.close();
}
@Override
protected Iterator<T> originIterator() {
return this.originIterator;
}
@Override
protected final boolean fetch() {
if (this.batchIterator != null && this.fetchFromBatch()) {
return true;
}
while (this.originIterator.hasNext()) {
T next = this.originIterator.next();
assert this.batchIterator == null;
// Do fetch
this.batchIterator = this.mapperCallback.apply(next);
if (this.batchIterator != null && this.fetchFromBatch()) {
return true;
}
}
return false;
}
protected boolean fetchFromBatch() {
E.checkNotNull(this.batchIterator, "mapper results");
while (this.batchIterator.hasNext()) {
R result = this.batchIterator.next();
if (result != null) {
assert this.current == none();
this.current = result;
return true;
}
}
this.resetBatchIterator();
return false;
}
protected final void resetBatchIterator() {
if (this.batchIterator == null) {
return;
}
close(this.batchIterator);
this.batchIterator = null;
}
}

View File

@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.iterator;
import java.util.Iterator;
import java.util.function.Function;
public class LimitIterator<T> extends WrappedIterator<T> {
private final Iterator<T> originIterator;
private final Function<T, Boolean> filterCallback;
public LimitIterator(Iterator<T> origin, Function<T, Boolean> filter) {
this.originIterator = origin;
this.filterCallback = filter;
}
@Override
protected Iterator<T> originIterator() {
return this.originIterator;
}
@Override
protected final boolean fetch() {
while (this.originIterator.hasNext()) {
T next = this.originIterator.next();
if (next == null) {
continue;
}
// Do filter
boolean reachLimit = this.filterCallback.apply(next);
if (reachLimit) {
this.closeOriginIterator();
return false;
}
assert this.current == none();
this.current = next;
return true;
}
return false;
}
protected final void closeOriginIterator() {
if (this.originIterator == null) {
return;
}
close(this.originIterator);
}
}

View File

@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.iterator;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.hugegraph.util.InsertionOrderUtil;
public class ListIterator<T> extends WrappedIterator<T> {
private final Iterator<T> originIterator;
private final Iterator<T> resultsIterator;
private final Collection<T> results;
public ListIterator(long capacity, Iterator<T> origin) {
List<T> results = InsertionOrderUtil.newList();
while (origin.hasNext()) {
if (capacity >= 0L && results.size() >= capacity) {
throw new IllegalArgumentException(
"The iterator exceeded capacity " + capacity);
}
results.add(origin.next());
}
this.originIterator = origin;
this.results = Collections.unmodifiableList(results);
this.resultsIterator = this.results.iterator();
}
public ListIterator(Collection<T> origin) {
this.originIterator = origin.iterator();
this.results = origin instanceof List ?
Collections.unmodifiableList((List<T>) origin) :
Collections.unmodifiableCollection(origin);
this.resultsIterator = this.results.iterator();
}
@Override
public void remove() {
this.resultsIterator.remove();
}
public Collection<T> list() {
return this.results;
}
@Override
protected boolean fetch() {
assert this.current == none();
if (!this.resultsIterator.hasNext()) {
return false;
}
this.current = this.resultsIterator.next();
return true;
}
@Override
protected Iterator<T> originIterator() {
return this.originIterator;
}
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.iterator;
import java.util.Iterator;
import java.util.function.Function;
public class MapperIterator<T, R> extends WrappedIterator<R> {
private final Iterator<T> originIterator;
private final Function<T, R> mapperCallback;
public MapperIterator(Iterator<T> origin, Function<T, R> mapper) {
this.originIterator = origin;
this.mapperCallback = mapper;
}
@Override
protected Iterator<T> originIterator() {
return this.originIterator;
}
@Override
protected final boolean fetch() {
while (this.originIterator.hasNext()) {
T next = this.originIterator.next();
R result = this.mapperCallback.apply(next);
if (result != null) {
assert this.current == none();
this.current = result;
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.iterator;
public interface Metadatable {
Object metadata(String meta, Object... args);
}

View File

@ -0,0 +1,99 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.iterator;
import java.util.Iterator;
import java.util.NoSuchElementException;
public abstract class WrappedIterator<R> implements CIter<R> {
private static final Object NONE = new Object();
protected R current;
public WrappedIterator() {
this.current = none();
}
@Override
public boolean hasNext() {
if (this.current != none()) {
return true;
}
return this.fetch();
}
@Override
public R next() {
if (this.current == none()) {
this.fetch();
if (this.current == none()) {
throw new NoSuchElementException();
}
}
R current = this.current;
this.current = none();
return current;
}
@Override
public void remove() {
Iterator<?> iterator = this.originIterator();
if (iterator == null) {
throw new NoSuchElementException(
"The origin iterator can't be null for removing");
}
iterator.remove();
}
@Override
public void close() throws Exception {
Iterator<?> iterator = this.originIterator();
if (iterator instanceof AutoCloseable) {
((AutoCloseable) iterator).close();
}
}
@Override
public Object metadata(String meta, Object... args) {
Iterator<?> iterator = this.originIterator();
if (iterator instanceof Metadatable) {
return ((Metadatable) iterator).metadata(meta, args);
}
throw new IllegalStateException("Original iterator is not Metadatable");
}
@SuppressWarnings("unchecked")
protected static final <R> R none() {
return (R) NONE;
}
public static void close(Iterator<?> iterator) {
if (iterator instanceof AutoCloseable) {
try {
((AutoCloseable) iterator).close();
} catch (Exception e) {
throw new IllegalStateException("Failed to close iterator");
}
}
}
protected abstract Iterator<?> originIterator();
protected abstract boolean fetch();
}

View File

@ -0,0 +1,106 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.license;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.time.DateUtils;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
public class LicenseCommonParam {
@JsonProperty("subject")
private String subject;
@JsonProperty("issued_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date issuedTime = new Date();
@JsonProperty("not_before")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date notBefore = this.issuedTime;
@JsonProperty("not_after")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date notAfter = DateUtils.addDays(this.notBefore, 30);
@JsonProperty("consumer_type")
private String consumerType = "user";
@JsonProperty("consumer_amount")
private Integer consumerAmount = 1;
@JsonProperty("description")
private String description = "";
@JsonProperty("extra_params")
private List<LicenseExtraParam> extraParams;
public LicenseCommonParam() {
// pass
}
public LicenseCommonParam(String subject, String description,
Date issued, Date notBefore, Date notAfter,
String consumerType, int consumerAmount,
List<LicenseExtraParam> extraParams) {
this.subject = subject;
this.description = description;
this.issuedTime = issued;
this.notBefore = notBefore;
this.notAfter = notAfter;
this.consumerType = consumerType;
this.consumerAmount = consumerAmount;
this.extraParams = extraParams;
}
public String subject() {
return this.subject;
}
public Date issuedTime() {
return this.issuedTime;
}
public Date notBefore() {
return this.notBefore;
}
public Date notAfter() {
return this.notAfter;
}
public String consumerType() {
return this.consumerType;
}
public Integer consumerAmount() {
return this.consumerAmount;
}
public String description() {
return this.description;
}
public List<LicenseExtraParam> extraParams() {
return this.extraParams;
}
}

View File

@ -0,0 +1,61 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.license;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
public class LicenseCreateParam extends LicenseCommonParam {
@JsonProperty("private_alias")
private String privateAlias;
@JsonAlias("key_ticket")
@JsonProperty("key_password")
private String keyPassword;
@JsonAlias("store_ticket")
@JsonProperty("store_password")
private String storePassword;
@JsonProperty("privatekey_path")
private String privateKeyPath;
@JsonProperty("license_path")
private String licensePath;
public String privateAlias() {
return this.privateAlias;
}
public String keyPassword() {
return this.keyPassword;
}
public String storePassword() {
return this.storePassword;
}
public String privateKeyPath() {
return this.privateKeyPath;
}
public String licensePath() {
return this.licensePath;
}
}

View File

@ -0,0 +1,119 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.license;
import com.fasterxml.jackson.annotation.JsonProperty;
public class LicenseExtraParam {
public static final int NO_LIMIT = -1;
@JsonProperty("id")
private String id;
@JsonProperty("version")
private String version;
@JsonProperty("graphs")
private int graphs;
@JsonProperty("ip")
private String ip;
@JsonProperty("mac")
private String mac;
@JsonProperty("cpus")
private int cpus;
// The unit is MB
@JsonProperty("ram")
private int ram;
@JsonProperty("threads")
private int threads;
// The unit is MB
@JsonProperty("memory")
private int memory;
@JsonProperty("nodes")
private int nodes;
// The unit is MB
@JsonProperty("data_size")
private long dataSize;
@JsonProperty("vertices")
private long vertices;
@JsonProperty("edges")
private long edges;
public String id() {
return this.id;
}
public String version() {
return this.version;
}
public int graphs() {
return this.graphs;
}
public String ip() {
return this.ip;
}
public String mac() {
return this.mac;
}
public int cpus() {
return this.cpus;
}
public int ram() {
return this.ram;
}
public int threads() {
return this.threads;
}
public int memory() {
return this.memory;
}
public int nodes() {
return this.nodes;
}
public long dataSize() {
return this.dataSize;
}
public long vertices() {
return this.vertices;
}
public long edges() {
return this.edges;
}
}

View File

@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.license;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
public class LicenseInstallParam {
@JsonProperty("subject")
private String subject;
@JsonProperty("public_alias")
private String publicAlias;
@JsonAlias("store_ticket")
@JsonProperty("store_password")
private String storePassword;
@JsonProperty("publickey_path")
private String publicKeyPath;
@JsonProperty("license_path")
private String licensePath;
public String subject() {
return this.subject;
}
public String publicAlias() {
return this.publicAlias;
}
public String storePassword() {
return this.storePassword;
}
public String licensePath() {
return this.licensePath;
}
public String publicKeyPath() {
return this.publicKeyPath;
}
}

View File

@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.license;
public interface LicenseManager {
LicenseParams installLicense() throws Exception;
void uninstallLicense() throws Exception;
LicenseParams verifyLicense() throws Exception;
interface VerifyCallback {
void onVerifyLicense(LicenseParams params) throws Exception;
}
}

View File

@ -0,0 +1,30 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.license;
import org.apache.commons.lang.NotImplementedException;
import org.apache.hugegraph.license.LicenseManager.VerifyCallback;
public class LicenseManagerFactory {
public static LicenseManager create(LicenseInstallParam param,
VerifyCallback veryfyCallback) {
throw new NotImplementedException("No LicenseManager available");
}
}

View File

@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.license;
import java.util.Date;
import java.util.List;
public class LicenseParams extends LicenseCommonParam {
public LicenseParams() {
super();
}
public LicenseParams(String subject, String description,
Date issued, Date notBefore, Date notAfter,
String consumerType, int consumerAmount,
List<LicenseExtraParam> extraParams) {
super(subject, description, issued, notBefore, notAfter,
consumerType, consumerAmount, extraParams);
}
public LicenseExtraParam matchParam(String id) {
for (LicenseExtraParam param : this.extraParams()) {
if (param.id().equals(id)) {
return param;
}
}
return null;
}
}

View File

@ -0,0 +1,126 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.license;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class MachineInfo {
private List<String> ipAddressList;
private List<String> macAddressList;
public MachineInfo() {
this.ipAddressList = null;
this.macAddressList = null;
}
public List<String> getIpAddress() {
if (this.ipAddressList != null) {
return this.ipAddressList;
}
this.ipAddressList = new ArrayList<>();
List<InetAddress> inetAddresses = this.getLocalAllInetAddress();
if (inetAddresses != null && !inetAddresses.isEmpty()) {
this.ipAddressList = inetAddresses.stream()
.map(InetAddress::getHostAddress)
.distinct()
.map(String::toLowerCase)
.collect(Collectors.toList());
}
return this.ipAddressList;
}
public List<String> getMacAddress() {
if (this.macAddressList != null) {
return this.macAddressList;
}
this.macAddressList = new ArrayList<>();
List<InetAddress> inetAddresses = this.getLocalAllInetAddress();
if (inetAddresses != null && !inetAddresses.isEmpty()) {
// Get the Mac address of all network interfaces
List<String> list = new ArrayList<>();
Set<String> uniqueValues = new HashSet<>();
for (InetAddress inetAddress : inetAddresses) {
String macByInetAddress = this.getMacByInetAddress(inetAddress);
if (uniqueValues.add(macByInetAddress)) {
list.add(macByInetAddress);
}
}
this.macAddressList = list;
}
return this.macAddressList;
}
public List<InetAddress> getLocalAllInetAddress() {
Enumeration<NetworkInterface> interfaces;
try {
interfaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
throw new RuntimeException("Failed to get network interfaces");
}
List<InetAddress> result = new ArrayList<>();
while (interfaces.hasMoreElements()) {
NetworkInterface nw = interfaces.nextElement();
for (Enumeration<InetAddress> inetAddresses = nw.getInetAddresses();
inetAddresses.hasMoreElements(); ) {
InetAddress inetAddr = inetAddresses.nextElement();
if (!inetAddr.isLoopbackAddress() &&
!inetAddr.isLinkLocalAddress() &&
!inetAddr.isMulticastAddress()) {
result.add(inetAddr);
}
}
}
return result;
}
public String getMacByInetAddress(InetAddress inetAddr) {
byte[] mac;
try {
mac = NetworkInterface.getByInetAddress(inetAddr)
.getHardwareAddress();
} catch (Exception e) {
throw new RuntimeException(String.format(
"Failed to get mac address for inet address '%s'",
inetAddr));
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mac.length; i++) {
if (i != 0) {
sb.append("-");
}
String temp = Integer.toHexString(mac[i] & 0xff);
if (temp.length() == 1) {
sb.append("0").append(temp);
} else {
sb.append(temp);
}
}
return sb.toString().toUpperCase();
}
}

View File

@ -0,0 +1,185 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.perf;
import java.util.List;
import org.apache.hugegraph.perf.PerfUtil.FastMap;
public final class LightStopwatch implements Stopwatch {
private long lastStartTime = -1L;
private long times = 0L;
private long totalCost = 0L;
private long totalChildrenTimes = -1L;
private final String name;
private final Path parent;
private final Path id;
private final FastMap<String, Stopwatch> children;
public LightStopwatch(String name, Stopwatch parent) {
this(name, parent.id());
parent.child(name, this);
}
public LightStopwatch(String name, Path parent) {
this.name = name;
this.parent = parent;
this.id = Stopwatch.id(parent, name);
this.children = new FastMap<>();
}
@Override
public Path id() {
return this.id;
}
@Override
public String name() {
return this.name;
}
@Override
public Path parent() {
return this.parent;
}
@Override
public void lastStartTime(long startTime) {
this.lastStartTime = startTime;
}
@Override
public void startTime(long startTime) {
this.times++;
this.lastStartTime = startTime;
}
@Override
public void endTime(long startTime) {
this.totalCost += PerfUtil.now() - this.lastStartTime;
}
@Override
public long times() {
return this.times;
}
@Override
public long totalTimes() {
if (this.totalChildrenTimes > 0L) {
return this.times + this.totalChildrenTimes;
}
return this.times;
}
@Override
public long totalChildrenTimes() {
return this.totalChildrenTimes;
}
@Override
public long totalCost() {
return this.totalCost;
}
@Override
public void totalCost(long totalCost) {
this.totalCost = totalCost;
}
@Override
public long minCost() {
return -1L;
}
@Override
public long maxCost() {
return -1L;
}
@Override
public long totalWasted() {
return 0L;
}
@Override
public long totalSelfWasted() {
return 0L;
}
@Override
public long totalChildrenWasted() {
return -1L;
}
@Override
public void fillChildrenTotal(List<Stopwatch> children) {
// Fill total times of children
this.totalChildrenTimes = children.stream().mapToLong(Stopwatch::totalTimes).sum();
}
@Override
public LightStopwatch copy() {
try {
return (LightStopwatch) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
@Override
public Stopwatch child(String name) {
return this.children.get(name);
}
@Override
public Stopwatch child(String name, Stopwatch watch) {
if (watch == null) {
return this.children.remove(name);
}
return this.children.put(name, watch);
}
@Override
public boolean empty() {
return this.children.size() == 0;
}
@Override
public void clear() {
this.lastStartTime = -1L;
this.times = 0L;
this.totalCost = 0L;
this.totalChildrenTimes = -1L;
this.children.clear();
}
@Override
public String toString() {
return String.format("{parent:%s,name:%s," +
"times:%s,totalChildrenTimes:%s,totalCost:%s}",
this.parent, this.name,
this.times, this.totalChildrenTimes,
this.totalCost);
}
}

View File

@ -0,0 +1,302 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.perf;
import java.util.List;
import java.util.function.BiFunction;
import org.apache.hugegraph.util.Log;
import org.slf4j.Logger;
import org.apache.hugegraph.testutil.Whitebox;
public final class NormalStopwatch implements Stopwatch {
private static final Logger LOG = Log.logger(Stopwatch.class);
private static final String MULTI_THREAD_ACCESS_ERROR =
"There may be multi-threaded access, ensure " +
"not call PerfUtil.profileSingleThread(true) when " +
"multithreading.";
private long lastStartTime = -1L;
private long times = 0L;
private long totalCost = 0L;
private long minCost = Long.MAX_VALUE;
private long maxCost = 0L;
private long totalSelfWasted = 0L;
private long totalChildrenWasted = -1L;
private long totalChildrenTimes = -1L;
private final String name;
private final Path parent;
private final Path id;
private final PerfUtil.FastMap<String, Stopwatch> children;
public NormalStopwatch(String name, Stopwatch parent) {
this(name, parent.id());
parent.child(name, this);
}
public NormalStopwatch(String name, Path parent) {
this.name = name;
this.parent = parent;
this.id = Stopwatch.id(parent, name);
this.children = new PerfUtil.FastMap<>();
}
@Override
public Path id() {
return this.id;
}
@Override
public String name() {
return this.name;
}
@Override
public Path parent() {
return this.parent;
}
@Override
public void lastStartTime(long startTime) {
this.lastStartTime = startTime;
}
@Override
public void startTime(long startTime) {
assert this.lastStartTime == -1L : MULTI_THREAD_ACCESS_ERROR;
this.times++;
this.lastStartTime = startTime;
long endTime = PerfUtil.now();
long wastedTime = endTime - startTime;
if (wastedTime <= 0L) {
wastedTime += eachStartWastedLost;
}
this.totalSelfWasted += wastedTime;
}
@Override
public void endTime(long startTime) {
assert startTime >= this.lastStartTime && this.lastStartTime != -1L :
MULTI_THREAD_ACCESS_ERROR;
long endTime = PerfUtil.now();
// The following code cost about 3ns~4ns
long wastedTime = endTime - startTime;
if (wastedTime <= 0L) {
wastedTime += eachEndWastedLost;
}
long cost = endTime - this.lastStartTime;
if (this.minCost > cost) {
this.minCost = cost;
}
if (this.maxCost < cost) {
this.maxCost = cost;
}
this.totalCost += cost;
this.totalSelfWasted += wastedTime;
this.lastStartTime = -1L;
}
@Override
public long times() {
return this.times;
}
@Override
public long totalCost() {
return this.totalCost;
}
@Override
public void totalCost(long totalCost) {
this.totalCost = totalCost;
}
@Override
public long minCost() {
return this.minCost;
}
@Override
public long maxCost() {
return this.maxCost;
}
@Override
public long totalTimes() {
if (this.totalChildrenTimes > 0L) {
return this.times + this.totalChildrenTimes;
}
return this.times;
}
@Override
public long totalChildrenTimes() {
return this.totalChildrenTimes;
}
@Override
public long totalWasted() {
if (this.totalChildrenWasted > 0L) {
return this.totalSelfWasted + this.totalChildrenWasted;
}
return this.totalSelfWasted;
}
@Override
public long totalSelfWasted() {
return this.totalSelfWasted;
}
@Override
public long totalChildrenWasted() {
return this.totalChildrenWasted;
}
@Override
public void fillChildrenTotal(List<Stopwatch> children) {
// Fill total wasted cost of children
this.totalChildrenWasted = children.stream().mapToLong(Stopwatch::totalWasted).sum();
// Fill total times of children
this.totalChildrenTimes = children.stream().mapToLong(Stopwatch::totalTimes).sum();
}
@Override
public Stopwatch copy() {
try {
return (Stopwatch) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
@Override
public Stopwatch child(String name) {
return this.children.get(name);
}
@Override
public Stopwatch child(String name, Stopwatch watch) {
if (watch == null) {
return this.children.remove(name);
}
return this.children.put(name, watch);
}
@Override
public boolean empty() {
return this.children.size() == 0;
}
@Override
public void clear() {
this.lastStartTime = -1L;
this.times = 0L;
this.totalCost = 0L;
this.minCost = Long.MAX_VALUE;
this.maxCost = 0L;
this.totalSelfWasted = 0L;
this.totalChildrenWasted = -1L;
this.totalChildrenTimes = -1L;
this.children.clear();
}
@Override
public String toString() {
return String.format("{parent:%s,name:%s," +
"times:%s,totalChildrenTimes:%s," +
"totalCost:%s,minCost:%s,maxCost:%s," +
"totalSelfWasted:%s,totalChildrenWasted:%s}",
this.parent, this.name,
this.times, this.totalChildrenTimes,
this.totalCost, this.minCost, this.maxCost,
this.totalSelfWasted, this.totalChildrenWasted);
}
private static long eachStartWastedLost = 0L;
private static long eachEndWastedLost = 0L;
protected static void initEachWastedLost() {
int times = 100000000;
PerfUtil.LocalStack<Stopwatch> callStack = Whitebox.getInternalState(
PerfUtil.instance(), "callStack");
long baseStart = PerfUtil.now();
for (int i = 0; i < times; i++) {
PerfUtil.instance();
}
long baseCost = PerfUtil.now() - baseStart;
BiFunction<String, Runnable, Long> testEachCost = (name, test) -> {
long start = PerfUtil.now();
test.run();
long end = PerfUtil.now();
long cost = end - start - baseCost;
if (cost < 0L) {
cost = 0L;
}
long eachCost = cost / times;
LOG.info("Wasted time test: cost={}ms, base_cost={}ms, {}={}ns",
cost / 1000000.0, baseCost / 1000000.0, name, eachCost);
return eachCost;
};
String startName = "each_start_cost";
eachStartWastedLost = testEachCost.apply(startName, () -> {
Stopwatch watch = PerfUtil.instance().start(startName);
PerfUtil.instance().end(startName);
for (int i = 0; i < times; i++) {
// Test call start()
PerfUtil.instance().start(startName);
// Mock end()
watch.lastStartTime(-1L);
callStack.pop();
}
});
String endName = "each_end_cost";
eachEndWastedLost = testEachCost.apply(endName, () -> {
Stopwatch watch = PerfUtil.instance().start(endName);
PerfUtil.instance().end(endName);
for (int i = 0; i < times; i++) {
// Mock start()
callStack.push(watch);
watch.lastStartTime(0L);
// Test call start()
PerfUtil.instance().end(endName);
watch.totalCost(0L);
}
});
}
}

View File

@ -0,0 +1,687 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.perf;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.Log;
import org.apache.hugegraph.util.ReflectionUtil;
import org.apache.hugegraph.func.TriFunction;
import org.apache.hugegraph.testutil.Assert.ThrowableConsumer;
import org.apache.hugegraph.perf.Stopwatch.Path;
import com.google.common.reflect.ClassPath.ClassInfo;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
public final class PerfUtil {
private static final Logger LOG = Log.logger(PerfUtil.class);
private static final int DEFAULT_CAPACITY = 1024;
private static final ThreadLocal<PerfUtil> INSTANCE = new ThreadLocal<>();
private static PerfUtil SINGLE_INSTANCE = null;
private static Thread SINGLE_THREAD = null;
private static LocalTimer LOCAL_TIMER = null;
private static boolean LIGHT_WATCH = false;
private final Map<Path, Stopwatch> stopwatches;
private final LocalStack<Stopwatch> callStack;
private final Stopwatch root;
private PerfUtil() {
this.stopwatches = new HashMap<>(DEFAULT_CAPACITY);
this.callStack = new LocalStack<>(DEFAULT_CAPACITY);
this.root = newStopwatch(Path.ROOT_NAME, Path.EMPTY);
}
public static PerfUtil instance() {
if (SINGLE_INSTANCE != null &&
SINGLE_THREAD == Thread.currentThread()) {
// Return the only one instance for single thread, for performance
return SINGLE_INSTANCE;
}
PerfUtil p = INSTANCE.get();
if (p == null) {
p = new PerfUtil();
INSTANCE.set(p);
}
return p;
}
public static void profileSingleThread(boolean yes) {
SINGLE_INSTANCE = yes ? PerfUtil.instance() : null;
SINGLE_THREAD = yes ? Thread.currentThread() : null;
}
public static void useLocalTimer(boolean yes) {
if (yes) {
if (LOCAL_TIMER != null) {
return;
}
LOCAL_TIMER = new LocalTimer();
try {
LOCAL_TIMER.startTimeUpdateLoop();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (!LIGHT_WATCH) {
NormalStopwatch.initEachWastedLost();
}
} else {
if (LOCAL_TIMER == null) {
return;
}
try {
LOCAL_TIMER.stop();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
LOCAL_TIMER = null;
}
}
}
public static void useLightStopwatch(boolean yes) {
if (yes != LIGHT_WATCH) {
PerfUtil instance = INSTANCE.get();
boolean empty = instance == null || instance.empty();
String message = "Please call clear() before switching " +
"light-stopwatch due to there is dirty watch";
E.checkArgument(empty, message);
}
LIGHT_WATCH = yes;
}
protected static long now() {
if (LOCAL_TIMER != null) {
return LOCAL_TIMER.now();
}
// System.nanoTime() cost about 40 ns each call
return System.nanoTime();
}
protected static Stopwatch newStopwatch(String name, Path parent) {
return LIGHT_WATCH ? new LightStopwatch(name, parent) :
new NormalStopwatch(name, parent);
}
protected static Stopwatch newStopwatch(String name, Stopwatch parent) {
return LIGHT_WATCH ? new LightStopwatch(name, parent) :
new NormalStopwatch(name, parent);
}
public Stopwatch start(String name) {
long start = now();
Stopwatch parent = this.callStack.empty() ?
this.root : this.callStack.peek();
// Get watch by name from local tree
Stopwatch watch = parent.child(name);
if (watch == null) {
watch = newStopwatch(name, parent);
assert !this.stopwatches.containsKey(watch.id()) : watch;
this.stopwatches.put(watch.id(), watch);
}
this.callStack.push(watch);
watch.startTime(start);
return watch;
}
public Stopwatch start2(String name) {
long start = now(); // cost 70 ns with System.nanoTime()
Path parent = this.callStack.empty() ?
Path.EMPTY : this.callStack.peek().id();
Path id = Stopwatch.id(parent, name); // cost 130
// Get watch by id from global map
Stopwatch watch = this.stopwatches.get(id); // cost 170
if (watch == null) {
watch = newStopwatch(name, parent);
this.stopwatches.put(watch.id(), watch); // cost 180
}
this.callStack.push(watch); // cost 190
watch.startTime(start);
return watch;
}
public void end(String name) {
long start = LIGHT_WATCH ? 0L : now();
Stopwatch watch = this.callStack.pop();
if (watch == null || watch.name() != name) {
throw new IllegalArgumentException("Invalid watch name: " + name);
}
watch.endTime(start);
}
public boolean empty() {
return this.stopwatches.isEmpty() && this.root.empty();
}
public void clear() {
String error = "Can't be cleared when the call has not ended yet";
E.checkState(this.callStack.empty(), error);
this.stopwatches.clear();
this.root.clear();
}
public void profilePackage(String... packages) throws Throwable {
Set<String> loadedClasses = new HashSet<>();
Function<String, Boolean> inPackage = (cls) -> {
for (String pkg : packages) {
if (cls.startsWith(pkg)) {
return true;
}
}
return false;
};
ThrowableConsumer<String> profileClassIfPresent = (cls) -> {
if (!loadedClasses.contains(cls)) {
// Profile super class
for (String s : ReflectionUtil.superClasses(cls)) {
if (!loadedClasses.contains(s) && inPackage.apply(s)) {
profileClass(s);
loadedClasses.add(s);
}
}
// Profile self class
profileClass(cls);
loadedClasses.add(cls);
}
};
Iterator<ClassInfo> classes = ReflectionUtil.classes(packages);
while (classes.hasNext()) {
String cls = classes.next().getName();
// Profile self class
profileClassIfPresent.accept(cls);
// Profile nested class
for (String s : ReflectionUtil.nestedClasses(cls)) {
profileClassIfPresent.accept(s);
}
}
}
public void profileClass(String... classes) throws Throwable {
ClassPool classPool = ClassPool.getDefault();
for (String cls : classes) {
CtClass ctClass = classPool.get(cls);
List<CtMethod> methods = ReflectionUtil.getMethodsAnnotatedWith(
ctClass, Watched.class, false);
for (CtMethod method : methods) {
profile(method);
}
// Load class and make it effective
if (!methods.isEmpty()) {
ctClass.toClass();
}
}
}
private void profile(CtMethod ctMethod)
throws CannotCompileException, ClassNotFoundException {
final String START =
"org.apache.hugegraph.perf.PerfUtil.instance().start(\"%s\");";
final String END =
"org.apache.hugegraph.perf.PerfUtil.instance().end(\"%s\");";
Watched annotation = (Watched) ctMethod.getAnnotation(Watched.class);
String name = annotation.value();
if (name.isEmpty()) {
name = ctMethod.getName();
}
if (!annotation.prefix().isEmpty()) {
name = annotation.prefix() + "." + name;
}
ctMethod.insertBefore(String.format(START, name));
// Insert as a finally-statement
ctMethod.insertAfter(String.format(END, name), true);
LOG.debug("Profiled for: '{}' [{}]", name, ctMethod.getLongName());
}
@Override
public String toString() {
return this.stopwatches.toString();
}
public String toJson() {
StringBuilder sb = new StringBuilder(8 + this.stopwatches.size() * 96);
sb.append('{');
for (Map.Entry<Path, Stopwatch> w : this.stopwatches.entrySet()) {
sb.append('"');
sb.append(w.getKey());
sb.append('"');
sb.append(':');
sb.append(w.getValue().toJson());
sb.append(',');
}
if (!this.stopwatches.isEmpty()) {
sb.deleteCharAt(sb.length() - 1);
}
sb.append('}');
return sb.toString();
}
// TODO: move toECharts() method out of this class
public String toECharts() {
TriFunction<Integer, Integer, List<Stopwatch>, String> formatLevel = (
totalDepth, depth, items) -> {
float factor = 100.0f / (totalDepth + 1);
float showFactor = 1 + (totalDepth - depth) / (float) depth;
float radiusFrom = depth * factor;
float radiusTo = depth * factor + factor;
if (depth == 1) {
radiusFrom = 0;
}
StringBuilder sb = new StringBuilder(8 + items.size() * 128);
sb.append('{');
sb.append("name: 'Total Cost',");
sb.append("type: 'pie',");
sb.append(String.format("radius: ['%s%%', '%s%%'],",
radiusFrom, radiusTo));
sb.append(String.format(
"label: {normal: {position: 'inner', formatter:" +
"function(params) {" +
" if (params.percent > %s) return params.data.name;" +
" else return '';" +
"}}},", showFactor));
sb.append("data: [");
items.sort((i, j) -> i.id().compareTo(j.id()));
for (Stopwatch w : items) {
sb.append('{');
sb.append("id:'");
sb.append(w.id());
sb.append("',");
sb.append("name:'");
sb.append(w.name());
sb.append("',");
sb.append("value:");
// w.totalCost() - w.totalWasted() ?
sb.append(w.totalCost());
sb.append(',');
sb.append("cost:");
sb.append(w.totalCost() / 1000000.0);
sb.append(',');
sb.append("minCost:");
sb.append(w.minCost());
sb.append(',');
sb.append("maxCost:");
sb.append(w.maxCost());
sb.append(',');
sb.append("wasted:");
sb.append(w.totalWasted() / 1000000.0);
sb.append(',');
sb.append("selfWasted:");
sb.append(w.totalSelfWasted() / 1000000.0);
sb.append(',');
sb.append("times:");
sb.append(w.times());
sb.append(',');
sb.append("totalTimes:");
sb.append(w.totalTimes());
sb.append('}');
sb.append(',');
}
if (!items.isEmpty()) {
sb.deleteCharAt(sb.length() - 1);
}
sb.append("]}");
return sb.toString();
};
BiConsumer<List<Stopwatch>, List<Stopwatch>> fillChildrenTotal =
(itemsOfLn, itemsOfLnParent) -> {
for (Stopwatch parent : itemsOfLnParent) {
List<Stopwatch> children = itemsOfLn.stream().filter(c -> {
return c.parent().equals(parent.id());
}).collect(Collectors.toList());
parent.fillChildrenTotal(children);
}
};
BiConsumer<List<Stopwatch>, List<Stopwatch>> fillOther =
(itemsOfLn, itemsOfLnParent) -> {
for (Stopwatch parent : itemsOfLnParent) {
Stream<Stopwatch> children = itemsOfLn.stream().filter(c -> {
return c.parent().equals(parent.id());
});
// Fill other cost
long sumCost = children.mapToLong(Stopwatch::totalCost).sum();
long otherCost = parent.totalCost() - sumCost;
if (otherCost > 0L) {
Stopwatch other = newStopwatch("~", parent.id());
other.totalCost(otherCost);
itemsOfLn.add(other);
}
}
};
Map<Path, Stopwatch> items = this.stopwatches;
Map<Integer, List<Stopwatch>> levelItems = new HashMap<>();
int maxDepth = 0;
for (Map.Entry<Path, Stopwatch> e : items.entrySet()) {
int depth = e.getKey().toString().split("/").length;
List<Stopwatch> levelItem = levelItems.get(depth);
if (levelItem == null) {
levelItem = new LinkedList<>();
levelItems.putIfAbsent(depth, levelItem);
}
levelItem.add(e.getValue().copy());
if (depth > maxDepth) {
maxDepth = depth;
}
}
// Fill wasted cost from the outermost to innermost
for (int i = maxDepth; i > 0; i--) {
assert levelItems.containsKey(i) : i;
List<Stopwatch> itemsOfI = levelItems.get(i);
List<Stopwatch> itemsOfParent = levelItems.get(i - 1);
if (itemsOfParent != null) {
// Fill total value of children
fillChildrenTotal.accept(itemsOfI, itemsOfParent);
}
}
StringBuilder sb = new StringBuilder(8 + items.size() * 128);
// Output results header
sb.append("{");
sb.append("tooltip: {trigger: 'item', " +
"formatter: function(params) {" +
" return params.data.name + ' ' + params.percent + '% <br/>'" +
" + 'cost: ' + params.data.cost + ' (ms) <br/>'" +
" + 'min cost: ' + params.data.minCost + ' (ns) <br/>'" +
" + 'max cost: ' + params.data.maxCost + ' (ns) <br/>'" +
" + 'wasted: ' + params.data.wasted + ' (ms) <br/>'" +
" + 'self wasted: ' + params.data.selfWasted + ' (ms) <br/>'" +
" + 'times: ' + params.data.times + '<br/>'" +
" + 'total times: ' + params.data.totalTimes + '<br/>'" +
" + 'path: ' + params.data.id + '<br/>';" +
"}");
sb.append("},");
sb.append("series: [");
// Output results data
for (int i = 1; i <= maxDepth; i++) {
assert levelItems.containsKey(i) : i;
List<Stopwatch> itemsOfI = levelItems.get(i);
List<Stopwatch> itemsOfParent = levelItems.get(i - 1);
if (itemsOfParent != null) {
// Fill other cost for non-root level, ignore root level (i=1)
fillOther.accept(itemsOfI, itemsOfParent);
}
// Output items of level I
sb.append(formatLevel.apply(maxDepth, i, itemsOfI));
sb.append(',');
}
if (!items.isEmpty()) {
sb.deleteCharAt(sb.length() - 1);
}
sb.append("]}");
return sb.toString();
}
public static final class LocalTimer {
// Header: 4 bytes classptr + 8 bytes markword
private volatile long padding11 = 0L;
private volatile long padding12 = 0L;
private volatile long padding13 = 0L;
private volatile long padding14 = 0L;
private volatile long padding15 = 0L;
private volatile long padding16 = 0L; // the 1st 64 bytes
private volatile long time = 0L;
private volatile long padding21 = 0L;
private volatile long padding22 = 0L;
private volatile long padding23 = 0L;
private volatile long padding24 = 0L;
private volatile long padding25 = 0L;
private volatile long padding26 = 0L;
private volatile long padding27 = 0L; // the 2nd 64 bytes
private volatile boolean running = false;
private Thread thread = null;
public long now() {
// Read current ns time (be called frequently)
return this.time;
}
public void startTimeUpdateLoop() throws InterruptedException {
assert this.thread == null;
assert this.preventOptimizePadding() == 0L;
this.running = true;
CountDownLatch started = new CountDownLatch(1);
this.thread = new Thread(() -> {
started.countDown();
while (this.running) {
this.time = System.nanoTime();
// Prevent frequent updates for perf (5.2s => 3.6s for 8kw)
Thread.yield();
}
}, "LocalTimer");
this.thread.setDaemon(true);
this.thread.start();
started.await();
}
public void stop() throws InterruptedException {
this.running = false;
if (this.thread != null) {
this.thread.join();
}
}
public long preventOptimizePadding() {
long p1 = this.padding11 + this.padding12 + this.padding13 +
this.padding14 + this.padding15 + this.padding16;
long p2 = this.padding21 + this.padding22 + this.padding23 +
this.padding24 + this.padding25 + this.padding26 +
this.padding27;
return p1 + p2;
}
}
public static final class LocalStack<T> {
private final Object[] elementData;
private int elementCount;
public LocalStack(int capacity) {
this.elementData = new Object[capacity];
this.elementCount = 0;
}
int size() {
return this.elementCount;
}
boolean empty() {
return this.elementCount == 0;
}
public void push(T elem) {
this.elementData[this.elementCount++] = elem;
}
public T pop() {
if (this.elementCount == 0) {
throw new EmptyStackException();
}
this.elementCount--;
@SuppressWarnings("unchecked")
T elem = (T) this.elementData[this.elementCount];
this.elementData[this.elementCount] = null;
return elem;
}
public T peek() {
if (this.elementCount == 0) {
throw new EmptyStackException();
}
@SuppressWarnings("unchecked")
T elem = (T) this.elementData[this.elementCount - 1];
return elem;
}
}
public static final class FastMap<K, V> {
private final Map<K, V> hashMap;
private K key1;
private K key2;
private K key3;
private V val1;
private V val2;
private V val3;
public FastMap() {
this.hashMap = new HashMap<>();
}
public int size() {
return this.hashMap.size();
}
public boolean containsKey(Object key) {
return this.hashMap.containsKey(key);
}
public V get(Object key) {
if (key == this.key1) {
return this.val1;
} else if (key == this.key2) {
return this.val2;
} else if (key == this.key3) {
return this.val3;
}
return this.hashMap.get(key);
}
public V put(K key, V value) {
if (this.key1 == null) {
this.key1 = key;
this.val1 = value;
} else if (this.key2 == null) {
this.key2 = key;
this.val2 = value;
} else if (this.key3 == null) {
this.key3 = key;
this.val3 = value;
}
return this.hashMap.put(key, value);
}
public V remove(Object key) {
if (key == this.key1) {
this.key1 = null;
this.val1 = null;
} else if (key == this.key2) {
this.key2 = null;
this.val2 = null;
} else if (key == this.key3) {
this.key3 = null;
this.val3 = null;
}
return this.hashMap.remove(key);
}
public void clear() {
this.key1 = null;
this.key2 = null;
this.key3 = null;
this.val1 = null;
this.val2 = null;
this.val3 = null;
this.hashMap.clear();
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR })
public @interface Watched {
String value() default "";
String prefix() default "";
}
}

View File

@ -0,0 +1,148 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.perf;
import java.util.List;
public interface Stopwatch extends Cloneable {
Path id();
String name();
Path parent();
void startTime(long startTime);
void endTime(long startTime);
void lastStartTime(long startTime);
long times();
long totalTimes();
long totalChildrenTimes();
long totalCost();
void totalCost(long otherCost);
long minCost();
long maxCost();
long totalWasted();
long totalSelfWasted();
long totalChildrenWasted();
void fillChildrenTotal(List<Stopwatch> children);
Stopwatch copy();
Stopwatch child(String name);
Stopwatch child(String name, Stopwatch watch);
boolean empty();
void clear();
default String toJson() {
int len = 200 + this.name().length() + this.parent().length();
StringBuilder sb = new StringBuilder(len);
sb.append("{");
sb.append("\"parent\":\"").append(this.parent()).append("\"");
sb.append(",\"name\":\"").append(this.name()).append("\"");
sb.append(",\"times\":").append(this.times());
sb.append(",\"total_cost\":").append(this.totalCost());
sb.append(",\"min_cost\":").append(this.minCost());
sb.append(",\"max_cost\":").append(this.maxCost());
sb.append(",\"total_self_wasted\":").append(this.totalSelfWasted());
sb.append(",\"total_children_wasted\":").append(
this.totalChildrenWasted());
sb.append(",\"total_children_times\":").append(
this.totalChildrenTimes());
sb.append("}");
return sb.toString();
}
static Path id(Path parent, String name) {
if (parent == Path.EMPTY && name == Path.ROOT_NAME) {
return Path.EMPTY;
}
return new Path(parent, name);
}
final class Path implements Comparable<Path> {
public static final String ROOT_NAME = "root";
public static final Path EMPTY = new Path("");
private final String path;
public Path(String self) {
this.path = self;
}
public Path(Path parent, String name) {
if (parent == EMPTY) {
this.path = name;
} else {
this.path = parent.path + '/' + name;
}
}
public int length() {
return this.path.length();
}
@Override
public int hashCode() {
return this.path.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this.hashCode() != obj.hashCode()) {
return false;
}
if (!(obj instanceof Path)) {
return false;
}
Path other = (Path) obj;
return this.path.equals(other.path);
}
@Override
public int compareTo(Path other) {
return this.path.compareTo(other.path);
}
@Override
public String toString() {
return this.path;
}
public boolean endsWith(String name) {
return this.path.endsWith(name);
}
}
}

View File

@ -0,0 +1,479 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.rest;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.commons.lang3.StringUtils;
import org.apache.hugegraph.util.JsonUtilCommon;
import org.jetbrains.annotations.NotNull;
import com.google.common.collect.ImmutableMap;
import lombok.SneakyThrows;
import okhttp3.ConnectionPool;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.BufferedSink;
import okio.GzipSink;
import okio.Okio;
/**
* This class provides an abstract implementation of the RestClient interface.
* It provides methods for making HTTP requests (GET, POST, PUT, DELETE) to a REST API.
* Note: It uses the OkHttp library to make these requests for now.
*/
public abstract class AbstractRestClient implements RestClient {
private final ThreadLocal<String> authContext;
private final OkHttpClient client;
private final String baseUrl;
public AbstractRestClient(String url, int timeout) {
this(url, RestClientConfig.builder().timeout(timeout).build());
}
public AbstractRestClient(String url, String user, String password, int timeout) {
this(url, RestClientConfig.builder()
.user(user)
.password(password)
.timeout(timeout)
.build());
}
public AbstractRestClient(String url, int timeout, int idleTime,
int maxConns, int maxConnsPerRoute) {
this(url, RestClientConfig.builder()
.idleTime(idleTime)
.timeout(timeout)
.maxConns(maxConns)
.maxConnsPerRoute(maxConnsPerRoute)
.build());
}
public AbstractRestClient(String url, String user, String password, int timeout,
int maxConns, int maxConnsPerRoute,
String trustStoreFile, String trustStorePassword) {
this(url, RestClientConfig.builder()
.user(user).password(password)
.timeout(timeout)
.maxConns(maxConns)
.maxConnsPerRoute(maxConnsPerRoute)
.trustStoreFile(trustStoreFile)
.trustStorePassword(trustStorePassword)
.build());
}
public AbstractRestClient(String url, String token, int timeout,
int maxConns, int maxConnsPerRoute,
String trustStoreFile, String trustStorePassword) {
this(url, RestClientConfig.builder()
.token(token)
.timeout(timeout)
.maxConns(maxConns)
.maxConnsPerRoute(maxConnsPerRoute)
.trustStoreFile(trustStoreFile)
.trustStorePassword(trustStorePassword)
.build());
}
public AbstractRestClient(String url, RestClientConfig config) {
this.baseUrl = url;
this.client = buildOkHttpClient(config);
this.authContext = new InheritableThreadLocal<>();
}
private static RequestBody buildRequestBody(Object body, RestHeaders headers) {
String contentType = parseContentType(headers);
String bodyContent;
if (RestHeaders.APPLICATION_JSON.equals(contentType)) {
if (body == null) {
bodyContent = "{}";
} else if (body instanceof String) {
bodyContent = (String) body;
} else {
bodyContent = JsonUtilCommon.toJson(body);
}
} else {
bodyContent = String.valueOf(body);
}
RequestBody requestBody = RequestBody.create(bodyContent.getBytes(),
MediaType.parse(contentType));
if (headers != null &&
"gzip".equals(headers.get(RestHeaders.CONTENT_ENCODING))) {
requestBody = gzipBody(requestBody);
}
return requestBody;
}
private static RequestBody gzipBody(final RequestBody body) {
return new RequestBody() {
@Override
public MediaType contentType() {
return body.contentType();
}
@Override
public long contentLength() {
return -1; // We don't know the compressed length in advance!
}
@Override
public void writeTo(@NotNull BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
private static String parseContentType(RestHeaders headers) {
if (headers != null) {
String contentType = headers.get(RestHeaders.CONTENT_TYPE);
if (contentType != null) {
return contentType;
}
}
return RestHeaders.APPLICATION_JSON;
}
private OkHttpClient buildOkHttpClient(RestClientConfig config) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (config.getTimeout() != null) {
builder.connectTimeout(config.getTimeout(), TimeUnit.MILLISECONDS)
.readTimeout(config.getTimeout(), TimeUnit.MILLISECONDS);
}
if (config.getConnectTimeout() != null) {
builder.connectTimeout(config.getConnectTimeout(), TimeUnit.MILLISECONDS);
}
if (config.getReadTimeout() != null) {
builder.readTimeout(config.getReadTimeout(), TimeUnit.MILLISECONDS);
}
if (config.getMaxIdleConns() != null || config.getIdleTime() != null) {
ConnectionPool connectionPool = new ConnectionPool(config.getMaxIdleConns(),
config.getIdleTime(),
TimeUnit.SECONDS);
builder.connectionPool(connectionPool);
}
// auth header interceptor
if (StringUtils.isNotBlank(config.getUser()) &&
StringUtils.isNotBlank(config.getPassword())) {
builder.addInterceptor(new OkHttpBasicAuthInterceptor(config.getUser(),
config.getPassword()));
}
if (StringUtils.isNotBlank(config.getToken())) {
builder.addInterceptor(new OkHttpTokenInterceptor(config.getToken()));
}
// ssl
configSsl(builder, this.baseUrl, config.getTrustStoreFile(),
config.getTrustStorePassword());
// Execute builder callback before builder.build() for user configs
if (config.getBuilderCallback() != null) {
config.getBuilderCallback().accept(builder);
}
OkHttpClient okHttpClient = builder.build();
if (config.getMaxConns() != null) {
okHttpClient.dispatcher().setMaxRequests(config.getMaxConns());
}
if (config.getMaxConnsPerRoute() != null) {
okHttpClient.dispatcher().setMaxRequestsPerHost(config.getMaxConnsPerRoute());
}
return okHttpClient;
}
@SneakyThrows
private void configSsl(OkHttpClient.Builder builder, String url, String trustStoreFile,
String trustStorePass) {
if (StringUtils.isBlank(trustStoreFile) || StringUtils.isBlank(trustStorePass)) {
return;
}
X509TrustManager trustManager = trustManagerForCertificates(trustStoreFile, trustStorePass);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
builder.sslSocketFactory(sslSocketFactory, trustManager)
.hostnameVerifier(new HostNameVerifier(url));
}
@Override
public RestResult post(String path, Object object) {
return this.post(path, object, null, null);
}
@Override
public RestResult post(String path, Object object, RestHeaders headers) {
return this.post(path, object, headers, null);
}
@Override
public RestResult post(String path, Object object, Map<String, Object> params) {
return this.post(path, object, null, params);
}
private Request.Builder genRequestBuilder(String path, String id, RestHeaders headers,
Map<String, Object> params) {
HttpUrl.Builder urlBuilder = Objects.requireNonNull(HttpUrl.parse(this.baseUrl))
.newBuilder()
.addPathSegments(path);
if (id != null) {
urlBuilder.addPathSegment(id);
}
if (params != null) {
params.forEach((name, value) -> {
if (value == null) {
return;
}
if (value instanceof Collection) {
for (Object i : (Collection<?>) value) {
urlBuilder.addQueryParameter(name, String.valueOf(i));
}
} else {
urlBuilder.addQueryParameter(name, String.valueOf(value));
}
});
}
Request.Builder builder = newRequestBuilder().url(urlBuilder.build());
if (headers != null) {
builder.headers(headers.toOkHttpHeader());
}
this.attachAuthToRequest(builder);
return builder;
}
/**
* In order to provide subclasses with overloading opportunities
*/
protected Request.Builder newRequestBuilder() {
return new Request.Builder();
}
@SneakyThrows
@Override
public RestResult post(String path, Object object, RestHeaders headers,
Map<String, Object> params) {
Request.Builder requestBuilder = genRequestBuilder(path, null, headers, params);
requestBuilder.post(buildRequestBody(object, headers));
try (Response response = request(requestBuilder)) {
checkStatus(response, 200, 201, 202);
return new RestResult(response);
}
}
@Override
public RestResult put(String path, String id, Object object) {
return this.put(path, id, object, ImmutableMap.of());
}
@Override
public RestResult put(String path, String id, Object object, RestHeaders headers) {
return this.put(path, id, object, headers, null);
}
@Override
public RestResult put(String path, String id, Object object, Map<String, Object> params) {
return this.put(path, id, object, null, params);
}
@SneakyThrows
@Override
public RestResult put(String path, String id, Object object,
RestHeaders headers,
Map<String, Object> params) {
Request.Builder requestBuilder = genRequestBuilder(path, id, headers, params);
requestBuilder.put(buildRequestBody(object, headers));
try (Response response = request(requestBuilder)) {
checkStatus(response, 200, 202);
return new RestResult(response);
}
}
@Override
public RestResult get(String path) {
return this.get(path, null, ImmutableMap.of());
}
@Override
public RestResult get(String path, Map<String, Object> params) {
return this.get(path, null, params);
}
@Override
public RestResult get(String path, String id) {
return this.get(path, id, ImmutableMap.of());
}
@SneakyThrows
private RestResult get(String path, String id, Map<String, Object> params) {
Request.Builder requestBuilder = genRequestBuilder(path, id, null, params);
try (Response response = request(requestBuilder)) {
checkStatus(response, 200);
return new RestResult(response);
}
}
@Override
public RestResult delete(String path, Map<String, Object> params) {
return this.delete(path, null, params);
}
@Override
public RestResult delete(String path, String id) {
return this.delete(path, id, ImmutableMap.of());
}
@SneakyThrows
private RestResult delete(String path, String id,
Map<String, Object> params) {
Request.Builder requestBuilder = genRequestBuilder(path, id, null, params);
requestBuilder.delete();
try (Response response = request(requestBuilder)) {
checkStatus(response, 204, 202);
return new RestResult(response);
}
}
protected abstract void checkStatus(Response response, int... statuses);
@SneakyThrows
protected Response request(Request.Builder requestBuilder) {
return this.client.newCall(requestBuilder.build()).execute();
}
@SneakyThrows
@Override
public void close() {
if (this.client != null) {
this.client.dispatcher().executorService().shutdown();
this.client.connectionPool().evictAll();
if (this.client.cache() != null) {
this.client.cache().close();
}
}
}
public void resetAuthContext() {
this.authContext.remove();
}
public String getAuthContext() {
return this.authContext.get();
}
public void setAuthContext(String auth) {
this.authContext.set(auth);
}
private void attachAuthToRequest(Request.Builder builder) {
// Add auth header
String auth = this.getAuthContext();
if (StringUtils.isNotEmpty(auth)) {
builder.addHeader(RestHeaders.AUTHORIZATION, auth);
}
}
@SneakyThrows
private X509TrustManager trustManagerForCertificates(String trustStoreFile,
String trustStorePass) {
char[] password = trustStorePass.toCharArray();
// load keyStore
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (FileInputStream in = new FileInputStream(trustStoreFile)) {
keyStore.load(in, password);
}
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" +
Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
}
public static class HostNameVerifier implements HostnameVerifier {
private final String url;
public HostNameVerifier(String url) {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url;
}
url = URI.create(url).getHost();
this.url = url;
}
@Override
public boolean verify(String hostname, SSLSession session) {
if (!this.url.isEmpty() && this.url.endsWith(hostname)) {
return true;
} else {
HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
return verifier.verify(hostname, session);
}
}
}
}

View File

@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.rest;
public class ClientException extends RuntimeException {
private static final long serialVersionUID = 814572040103754705L;
public ClientException(String message, Throwable cause) {
super(message, cause);
}
public ClientException(String message, Object... args) {
super(String.format(message, args));
}
public ClientException(String message, Throwable cause, Object... args) {
super(String.format(message, args), cause);
}
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.rest;
import java.io.IOException;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class OkHttpBasicAuthInterceptor implements Interceptor {
private final String credentials;
public OkHttpBasicAuthInterceptor(String user, String password) {
this.credentials = Credentials.basic(user, password);
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (request.header(RestHeaders.AUTHORIZATION) == null) {
Request authenticatedRequest = request.newBuilder()
.header(RestHeaders.AUTHORIZATION,
this.credentials)
.build();
return chain.proceed(authenticatedRequest);
}
return chain.proceed(request);
}
}

View File

@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.rest;
import static org.apache.hugegraph.rest.RestHeaders.AUTHORIZATION;
import static org.apache.hugegraph.rest.RestHeaders.BEARER_PREFIX;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class OkHttpTokenInterceptor implements Interceptor {
private final String token;
public OkHttpTokenInterceptor(String token) {
this.token = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (request.header(AUTHORIZATION) == null) {
Request authenticatedRequest = request.newBuilder()
.header(AUTHORIZATION,
BEARER_PREFIX + this.token)
.build();
return chain.proceed(authenticatedRequest);
}
return chain.proceed(request);
}
}

View File

@ -0,0 +1,63 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.rest;
import java.util.Map;
public interface RestClient {
/**
* Post method
*/
RestResult post(String path, Object object);
RestResult post(String path, Object object, RestHeaders headers);
RestResult post(String path, Object object, Map<String, Object> params);
RestResult post(String path, Object object, RestHeaders headers, Map<String, Object> params);
/**
* Put method
*/
RestResult put(String path, String id, Object object);
RestResult put(String path, String id, Object object, RestHeaders headers);
RestResult put(String path, String id, Object object, Map<String, Object> params);
RestResult put(String path, String id, Object object, RestHeaders headers,
Map<String, Object> params);
/**
* Get method
*/
RestResult get(String path);
RestResult get(String path, Map<String, Object> params);
RestResult get(String path, String id);
/**
* Delete method
*/
RestResult delete(String path, Map<String, Object> params);
RestResult delete(String path, String id);
void close();
}

View File

@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.rest;
import java.util.function.Consumer;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import okhttp3.OkHttpClient;
@Builder
@Getter
@Setter
@SuppressWarnings("unused")
public class RestClientConfig {
private String user;
private String password;
private String token;
/**
* @deprecated use connectTimeout and readTimeout instead
*/
@Deprecated
private Integer timeout;
/** unit in milliseconds */
private Integer connectTimeout;
/** unit in milliseconds */
private Integer readTimeout;
private Integer maxConns;
private Integer maxConnsPerRoute;
// unit in seconds
private Integer idleTime = 30;
private Integer maxIdleConns = 5;
private String trustStoreFile;
private String trustStorePassword;
private Consumer<OkHttpClient.Builder> builderCallback;
}

View File

@ -0,0 +1,90 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.rest;
import java.util.Date;
import java.util.Iterator;
import kotlin.Pair;
public class RestHeaders {
public static final String CONTENT_TYPE = "Content-Type";
public static final String CONTENT_ENCODING = "Content-Encoding";
public static final String AUTHORIZATION = "Authorization";
public static final String APPLICATION_JSON = "application/json";
public static final String BEARER_PREFIX = "Bearer ";
private final okhttp3.Headers.Builder headersBuilder;
public RestHeaders() {
this.headersBuilder = new okhttp3.Headers.Builder();
}
public static RestHeaders convertToRestHeaders(okhttp3.Headers headers) {
RestHeaders restHeaders = new RestHeaders();
if (headers != null) {
Iterator<Pair<String, String>> iter = headers.iterator();
while (iter.hasNext()) {
Pair<String, String> pair = iter.next();
restHeaders.add(pair.getFirst(), pair.getSecond());
}
}
return restHeaders;
}
public String get(String key) {
return this.headersBuilder.get(key);
}
public Date getDate(String key) {
return this.headersBuilder.build().getDate(key);
}
public RestHeaders add(String key, String value) {
this.headersBuilder.add(key, value);
return this;
}
public RestHeaders add(String key, Date value) {
this.headersBuilder.add(key, value);
return this;
}
@Override
public int hashCode() {
return this.toOkHttpHeader().hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof RestHeaders) {
return this.toOkHttpHeader().equals(((RestHeaders) obj).toOkHttpHeader());
}
return false;
}
public okhttp3.Headers toOkHttpHeader() {
return this.headersBuilder.build();
}
}

View File

@ -0,0 +1,114 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.rest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import okhttp3.Response;
public class RestResult {
private static final ObjectMapper MAPPER = new ObjectMapper();
private final int status;
private final RestHeaders headers;
private final String content;
public RestResult(Response response) {
this(response.code(), getResponseContent(response),
RestHeaders.convertToRestHeaders(response.headers()));
}
public RestResult(int status, String content, RestHeaders headers) {
this.status = status;
this.headers = headers;
this.content = content;
}
@SneakyThrows
private static String getResponseContent(Response response) {
return response.body().string();
}
public static void registerModule(Module module) {
MAPPER.registerModule(module);
}
public int status() {
return this.status;
}
public RestHeaders headers() {
return this.headers;
}
public String content() {
return this.content;
}
public <T> T readObject(Class<T> clazz) {
try {
return MAPPER.readValue(this.content, clazz);
} catch (Exception e) {
throw new SerializeException("Failed to deserialize: %s", e, this.content);
}
}
@SuppressWarnings("deprecation")
public <T> List<T> readList(String key, Class<T> clazz) {
try {
JsonNode root = MAPPER.readTree(this.content);
JsonNode element = root.get(key);
if (element == null) {
throw new SerializeException("Can't find value of the key: %s in json.", key);
}
JavaType type = MAPPER.getTypeFactory()
.constructParametrizedType(ArrayList.class,
List.class, clazz);
return MAPPER.convertValue(element, type);
} catch (IOException e) {
throw new SerializeException("Failed to deserialize %s", e, this.content);
}
}
@SuppressWarnings("deprecation")
public <T> List<T> readList(Class<T> clazz) {
JavaType type = MAPPER.getTypeFactory()
.constructParametrizedType(ArrayList.class,
List.class, clazz);
try {
return MAPPER.readValue(this.content, type);
} catch (IOException e) {
throw new SerializeException("Failed to deserialize %s", e, this.content);
}
}
@Override
public String toString() {
return String.format("{status=%s, headers=%s, content=%s}", this.status, this.headers,
this.content);
}
}

View File

@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.rest;
public class SerializeException extends ClientException {
private static final long serialVersionUID = -4622753445618619311L;
public SerializeException(String message, Throwable e) {
super(message, e);
}
public SerializeException(String message, Object... args) {
super(message, args);
}
public SerializeException(String message, Throwable e, Object... args) {
super(message, e, args);
}
}

View File

@ -0,0 +1,164 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.testutil;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hamcrest.BaseMatcher;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Description;
public class Assert extends org.junit.Assert {
@FunctionalInterface
public interface ThrowableRunnable {
void run() throws Throwable;
}
@FunctionalInterface
public interface ThrowableConsumer<T> {
void accept(T t) throws Throwable;
}
public static void assertThrows(Class<? extends Throwable> clazz,
ThrowableRunnable runnable,
Consumer<Throwable> exceptionConsumer) {
Throwable expectedException = assertThrows(clazz, runnable);
assert expectedException != null;
exceptionConsumer.accept(expectedException);
}
public static Throwable assertThrows(Class<? extends Throwable> clazz,
ThrowableRunnable runnable) {
try {
// expect throwing here
runnable.run();
} catch (Throwable e) {
if (!clazz.isInstance(e)) {
// exception type not matched
Assert.fail(String.format("Bad exception type %s(expected %s)",
e.getClass().getName(), clazz.getName()));
}
return e;
}
// no exception
Assert.fail(String.format("No exception was thrown(expected %s)",
clazz.getName()));
// unavailable
assert false;
return null;
}
public static void assertEquals(byte expected, Object actual) {
org.junit.Assert.assertEquals(expected, actual);
}
public static void assertEquals(short expected, Object actual) {
org.junit.Assert.assertEquals(expected, actual);
}
public static void assertEquals(char expected, Object actual) {
org.junit.Assert.assertEquals(expected, actual);
}
public static void assertEquals(int expected, Object actual) {
org.junit.Assert.assertEquals(expected, actual);
}
public static void assertEquals(long expected, Object actual) {
org.junit.Assert.assertEquals(expected, actual);
}
public static void assertEquals(float expected, Object actual) {
org.junit.Assert.assertEquals(expected, actual);
}
public static void assertEquals(double expected, Object actual) {
org.junit.Assert.assertEquals(expected, actual);
}
@SuppressWarnings("deprecation")
public static void assertGt(Number expected, Object actual) {
org.junit.Assert.assertThat(actual, new NumberMatcher(expected, cmp -> {
return cmp > 0;
}, ">"));
}
@SuppressWarnings("deprecation")
public static void assertGte(Number expected, Object actual) {
org.junit.Assert.assertThat(actual, new NumberMatcher(expected, cmp -> {
return cmp >= 0;
}, ">="));
}
@SuppressWarnings("deprecation")
public static void assertLt(Number expected, Object actual) {
org.junit.Assert.assertThat(actual, new NumberMatcher(expected, cmp -> {
return cmp < 0;
}, "<"));
}
@SuppressWarnings("deprecation")
public static void assertLte(Number expected, Object actual) {
org.junit.Assert.assertThat(actual, new NumberMatcher(expected, cmp -> {
return cmp <= 0;
}, "<="));
}
@SuppressWarnings("deprecation")
public static void assertContains(String sub, String actual) {
org.junit.Assert.assertThat(actual, CoreMatchers.containsString(sub));
}
@SuppressWarnings("deprecation")
public static void assertInstanceOf(Class<?> clazz, Object object) {
org.junit.Assert.assertThat(object, CoreMatchers.instanceOf(clazz));
}
private static class NumberMatcher extends BaseMatcher<Object> {
private final String symbol;
private final Number expected;
private final Function<Integer, Boolean> cmp;
NumberMatcher(Number expected, Function<Integer, Boolean> cmp, String symbol) {
this.expected = expected;
this.cmp = cmp;
this.symbol = symbol;
}
@SuppressWarnings("unchecked")
@Override
public boolean matches(Object actual) {
Assert.assertInstanceOf(this.expected.getClass(), actual);
Assert.assertInstanceOf(Comparable.class, actual);
int cmp = ((Comparable<Number>) actual).compareTo(this.expected);
return this.cmp.apply(cmp);
}
@Override
public void describeTo(Description desc) {
desc.appendText("a number ").appendText(this.symbol)
.appendText(" ").appendText(this.expected.toString());
}
}
}

View File

@ -0,0 +1,184 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.testutil;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import org.apache.hugegraph.util.E;
import com.google.common.primitives.Primitives;
public class Whitebox {
public static final char SEPARATOR = '.';
public static void setInternalState(Object target,
String fieldName, Object value) {
assert target != null;
assert fieldName != null;
int sep = fieldName.lastIndexOf(SEPARATOR);
if (sep > 0) {
String prefix = fieldName.substring(0, sep);
Object result = getInternalState(target, prefix);
E.checkArgument(result != null,
"Can't set value on null field: `%s.%s`",
target, prefix);
target = result;
fieldName = fieldName.substring(sep + 1);
}
Class<?> c = target instanceof Class<?> ?
(Class<?>) target : target.getClass();
try {
Field f = getFieldFromHierarchy(c, fieldName);
f.setAccessible(true);
f.set(target, value);
} catch (Exception e) {
throw new RuntimeException(String.format(
"Can't set value of '%s' against object '%s'",
fieldName, target), e);
}
}
public static <T> T getInternalState(Object target, String fieldName) {
assert fieldName != null;
int sep = fieldName.indexOf(SEPARATOR);
if (sep > 0) {
String field = fieldName.substring(0, sep);
Object value = getInternalState(target, field);
field = fieldName.substring(sep + 1);
return getInternalState(value, field);
}
Class<?> c = target instanceof Class<?> ?
(Class<?>) target : target.getClass();
try {
Field f = getFieldFromHierarchy(c, fieldName);
f.setAccessible(true);
@SuppressWarnings("unchecked")
T result = (T) f.get(target);
return result;
} catch (Exception e) {
throw new RuntimeException(String.format(
"Unable to get internal state on field '%s' of %s",
fieldName, target), e);
}
}
private static Field getFieldFromHierarchy(Class<?> clazz, String field) {
Field f = getField(clazz, field);
while (f == null && clazz != Object.class) {
clazz = clazz.getSuperclass();
f = getField(clazz, field);
}
if (f == null) {
throw new RuntimeException(String.format(
"Not declared field '%s' in class '%s'",
field, clazz.getSimpleName()));
}
return f;
}
private static Field getField(Class<?> clazz, String field) {
try {
return clazz.getDeclaredField(field);
} catch (NoSuchFieldException e) {
return null;
}
}
public static <T> T invokeStatic(Class<?> clazz, String methodName,
Object... args) {
return invoke(clazz, methodName, (Object) null, args);
}
public static <T> T invokeStatic(Class<?> clazz, Class<?>[] classes,
String methodName, Object... args) {
return invoke(clazz, classes, methodName, (Object) null, args);
}
public static <T> T invoke(Object owner, String field,
String methodName, Object... args) {
Object self = getInternalState(owner, field);
Objects.requireNonNull(self);
return invoke(self.getClass(), methodName, self, args);
}
public static <T> T invoke(Object owner, String field, Class<?>[] classes,
String methodName, Object... args) {
Object self = getInternalState(owner, field);
Objects.requireNonNull(self);
return invoke(self.getClass(), classes, methodName, self, args);
}
public static <T> T invoke(Class<?> clazz, String methodName,
Object self, Object... args) {
Class<?>[] classes = new Class<?>[args.length];
int i = 0;
for (Object arg : args) {
E.checkArgument(arg != null, "The argument can't be null");
classes[i++] = Primitives.unwrap(arg.getClass());
}
return invoke(clazz, classes, methodName, self, args);
}
public static <T> T invoke(Class<?> clazz, Class<?>[] classes,
String methodName, Object self, Object... args) {
Method method = method(clazz, methodName, classes);
try {
method.setAccessible(true);
@SuppressWarnings("unchecked")
T result = (T) method.invoke(self, args);
return result;
} catch (IllegalAccessException e) {
throw new RuntimeException(String.format(
"Can't invoke method '%s' of class '%s': %s",
methodName, clazz, e.getMessage()), e);
} catch (InvocationTargetException e) {
Throwable target = e.getTargetException();
if (target instanceof RuntimeException) {
throw (RuntimeException) target;
}
throw new RuntimeException(String.format(
"Can't invoke method '%s' of class '%s': %s",
methodName, clazz, target.getMessage()), target);
}
}
public static Method method(Class<?> clazz, String methodName,
Class<?>[] argsClasses) {
try {
return clazz.getDeclaredMethod(methodName, argsClasses);
} catch (NoSuchMethodException e) {
Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {
try {
return method(superclass, methodName, argsClasses);
} catch (Exception ignored) {
// pass
}
}
throw new RuntimeException(String.format(
"Can't find method '%s' with args %s of class '%s'",
methodName, Arrays.asList(argsClasses), clazz), e);
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.util.Arrays;
import java.util.Comparator;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import com.google.common.primitives.UnsignedBytes;
/**
* TODO: extends com.google.common.primitives.Bytes
*/
public final class Bytes {
public static final long BASE = 1024L;
public static final long KB = BASE;
public static final long MB = KB * BASE;
public static final long GB = MB * BASE;
public static final long TB = GB * KB;
public static final long PB = GB * MB;
public static final long EB = GB * GB;
private static final Comparator<byte[]> CMP = UnsignedBytes.lexicographicalComparator();
public static int compare(byte[] bytes1, byte[] bytes2) {
return CMP.compare(bytes1, bytes2);
}
public static byte[] concat(byte[] bytes1, byte[] bytes2) {
byte[] result = new byte[bytes1.length + bytes2.length];
System.arraycopy(bytes1, 0, result, 0, bytes1.length);
System.arraycopy(bytes2, 0, result, bytes1.length, bytes2.length);
return result;
}
public static boolean prefixWith(byte[] bytes, byte[] prefix) {
if (bytes.length < prefix.length) {
return false;
}
for (int i = 0; i < prefix.length; i++) {
if (bytes[i] != prefix[i]) {
return false;
}
}
return true;
}
public static boolean contains(byte[] bytes, byte value) {
for (byte b : bytes) {
if (b == value) {
return true;
}
}
return false;
}
public static int indexOf(byte[] bytes, byte value) {
for (int i = 0; i < bytes.length; i++) {
if (bytes[i] == value) {
return i;
}
}
return -1;
}
public static boolean equals(byte[] bytes1, byte[] bytes2) {
return Arrays.equals(bytes1, bytes2);
}
public static String toHex(byte b) {
return toHex(new byte[]{b});
}
public static String toHex(byte[] bytes) {
return new String(Hex.encodeHex(bytes));
}
public static byte[] fromHex(String hex) {
try {
return Hex.decodeHex(hex.toCharArray());
} catch (DecoderException e) {
throw new RuntimeException("Failed to decode hex: " + hex, e);
}
}
}

View File

@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import static java.lang.System.exit;
import java.net.InetAddress;
import java.net.Socket;
public final class CheckSocket {
public static void main(String[] args) {
try {
// Check if the socket connection can be closed normally
new Socket(InetAddress.getByName(args[0]), Integer.parseInt(args[1])).close();
exit(0);
} catch (Exception e) {
e.printStackTrace();
exit(-1);
}
}
}

View File

@ -0,0 +1,448 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.lang.reflect.Array;
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.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
public final class CollectionUtil {
public static <T> Set<T> toSet(Object object) {
E.checkNotNull(object, "object");
Set<T> set = InsertionOrderUtil.newSet();
fillCollection(set, object);
return set;
}
public static <T> List<T> toList(Object object) {
E.checkNotNull(object, "object");
List<T> list = new ArrayList<>();
fillCollection(list, object);
return list;
}
@SuppressWarnings("unchecked")
private static <T> void fillCollection(Collection<T> collection,
Object object) {
if (object.getClass().isArray()) {
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
collection.add((T) Array.get(object, i));
}
} else if (object instanceof Collection) {
collection.addAll((Collection<T>) object);
} else {
collection.add((T) object);
}
}
public static <T> boolean prefixOf(List<T> prefix, List<T> all) {
E.checkNotNull(prefix, "prefix");
E.checkNotNull(all, "all");
if (prefix.size() > all.size()) {
return false;
}
for (int i = 0; i < prefix.size(); i++) {
T first = prefix.get(i);
T second = all.get(i);
if (first == second) {
continue;
}
if (first == null || !first.equals(second)) {
return false;
}
}
return true;
}
public static Set<Integer> randomSet(int min, int max, int count) {
E.checkArgument(max > min, "Invalid min/max: %s/%s", min, max);
E.checkArgument(0 < count && count <= max - min,
"Invalid count %s", count);
Set<Integer> randoms = new HashSet<>();
while (randoms.size() < count) {
randoms.add(ThreadLocalRandom.current().nextInt(min, max));
}
return randoms;
}
public static boolean allUnique(Collection<?> collection) {
HashSet<Object> set = new HashSet<>(collection.size());
for (Object elem : collection) {
if (!set.add(elem)) {
return false;
}
}
return true;
}
/**
* Get sub-set of a set.
* @param original original set
* @param from index of start position
* @param to index of end position(exclude), but -1 means the last element
* @param <T> element type of set
* @return sub-set of original set [from, to)
*/
public static <T> Set<T> subSet(Set<T> original, int from, int to) {
E.checkArgument(from >= 0,
"Invalid from parameter of subSet(): %s", from);
if (to < 0) {
to = original.size();
} else {
E.checkArgument(to >= from,
"Invalid to parameter of subSet(): %s", to);
}
List<T> list = new ArrayList<>(original);
return new LinkedHashSet<>(list.subList(from, to));
}
public static <T> Set<T> union(Collection<T> first, Collection<T> second) {
E.checkNotNull(first, "first");
E.checkNotNull(second, "second");
HashSet<T> results = new HashSet<>(first);
results.addAll(second);
return results;
}
/**
* Find the intersection of two collections, the original collections
* will not be modified
* @param first the first collection
* @param second the second collection
* @param <T> element type of collection
* @return intersection of the two collections
*/
public static <T> Collection<T> intersect(Collection<T> first,
Collection<T> second) {
E.checkNotNull(first, "first");
E.checkNotNull(second, "second");
HashSet<T> results;
if (first instanceof HashSet) {
@SuppressWarnings("unchecked")
HashSet<T> clone = (HashSet<T>) ((HashSet<T>) first).clone();
results = clone;
} else {
results = new HashSet<>(first);
}
results.retainAll(second);
return results;
}
/**
* Find the intersection of two collections, note that the first collection
* will be modified and store the results
* @param first the first collection, it will be modified
* @param second the second collection
* @param <T> element type of collection
* @return intersection of the two collections
*/
public static <T> Collection<T> intersectWithModify(Collection<T> first,
Collection<T> second) {
E.checkNotNull(first, "first");
E.checkNotNull(second, "second");
first.retainAll(second);
return first;
}
public static <T> boolean hasIntersection(List<T> first, Set<T> second) {
E.checkNotNull(first, "first");
E.checkNotNull(second, "second");
for (T firstElem : first) {
if (second.contains(firstElem)) {
return true;
}
}
return false;
}
public static <T> boolean hasIntersection(Set<T> first, Set<T> second) {
E.checkNotNull(first, "first");
E.checkNotNull(second, "second");
if (first.size() <= second.size()) {
for (T firstElem : first) {
if (second.contains(firstElem)) {
return true;
}
}
} else {
for (T secondElem : second) {
if (first.contains(secondElem)) {
return true;
}
}
}
return false;
}
public static <K extends Comparable<? super K>, V> Map<K, V> sortByKey(
Map<K, V> map, boolean incr) {
List<Map.Entry<K, V>> list = new ArrayList<>(map.entrySet());
if (incr) {
list.sort(Map.Entry.comparingByKey());
} else {
list.sort(Collections.reverseOrder(Map.Entry.comparingByKey()));
}
Map<K, V> result = new LinkedHashMap<>();
for (Map.Entry<K, V> entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(
Map<K, V> map, boolean incr) {
List<Map.Entry<K, V>> list = new ArrayList<>(map.entrySet());
if (incr) {
list.sort(Map.Entry.comparingByValue());
} else {
list.sort(Collections.reverseOrder(Map.Entry.comparingByValue()));
}
Map<K, V> result = new LinkedHashMap<>();
for (Map.Entry<K, V> entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
/**
* Cross combine the A(n, n) combinations of each part in parts
* @param parts a list of part, like [{a,b}, {1,2}, {x,y}]
* @return List<List<Integer>> all combinations like
* [{a,b,1,2,x,y}, {a,b,1,2,y,x}, {a,b,2,1,x,y}, {a,b,2,1,y,x}...]
*/
public static <T> List<List<T>> crossCombineParts(List<List<T>> parts) {
List<List<T>> results = new ArrayList<>();
Deque<List<T>> selected = new ArrayDeque<>();
crossCombineParts(parts, 0, selected, results);
return results;
}
private static <T> void crossCombineParts(List<List<T>> parts,
int level,
Deque<List<T>> selected,
List<List<T>> results) {
assert level < parts.size();
List<T> part = parts.get(level);
for (List<T> combination : anm(part)) {
selected.addLast(combination);
if (level < parts.size() - 1) {
crossCombineParts(parts, level + 1, selected, results);
} else if (level == parts.size() - 1) {
results.add(flatToList(selected));
}
selected.removeLast();
}
}
private static <T> List<T> flatToList(Deque<List<T>> parts) {
List<T> list = new ArrayList<>();
for (List<T> part : parts) {
list.addAll(part);
}
return list;
}
/**
* Traverse C(n, m) combinations of a list
* @param all list to contain all items for combination
* @param n m of C(n, m)
* @param m n of C(n, m)
* @return List<List<T>> all combinations
*/
public static <T> List<List<T>> cnm(List<T> all, int n, int m) {
List<List<T>> combs = new ArrayList<>();
cnm(all, n, m, comb -> {
combs.add(comb);
return false;
});
return combs;
}
/**
* Traverse C(n, m) combinations of a list to find first matched
* result combination and call back with the result.
* @param all list to contain all items for combination
* @param n m of C(n, m)
* @param m n of C(n, m)
* @return true if matched any kind of items combination else false, the
* callback can always return false if you want to traverse all combinations
*/
public static <T> boolean cnm(List<T> all, int n, int m,
Function<List<T>, Boolean> callback) {
return cnm(all, n, m, 0, null, callback);
}
/**
* Traverse C(n, m) combinations of a list to find first matched
* result combination and call back with the result.
* @param all list to contain all items for combination
* @param n n of C(n, m)
* @param m m of C(n, m)
* @param current current position in list
* @param selected list to contain selected items
* @return true if matched any kind of items combination else false, the
* callback can always return false if you want to traverse all combinations
*/
private static <T> boolean cnm(List<T> all, int n, int m,
int current, List<T> selected,
Function<List<T>, Boolean> callback) {
assert n <= all.size();
assert m <= n;
assert current <= all.size();
if (selected == null) {
selected = new ArrayList<>(m);
}
if (m == 0) {
assert selected.size() > 0 : selected;
// All n items are selected
List<T> tmpResult = Collections.unmodifiableList(selected);
return callback.apply(tmpResult);
}
if (n == m) {
// No choice, select all n items, we don't update the `result` here
List<T> tmpResult = new ArrayList<>(selected);
tmpResult.addAll(all.subList(current, all.size()));
return callback.apply(tmpResult);
}
if (current >= all.size()) {
// Reach the end of items
return false;
}
// Select current item, continue to select C(m-1, n-1)
int index = selected.size();
selected.add(all.get(current));
++current;
if (cnm(all, n - 1, m - 1, current, selected, callback)) {
// NOTE: we can pop the tailing items if want to keep result clear
return true;
}
// Not select current item, pop it and continue to select C(m-1, n)
selected.remove(index);
assert selected.size() == index : selected;
return cnm(all, n - 1, m, current, selected, callback);
}
/**
* Traverse A(n, m) combinations of a list with n = m = all.size()
* @param all list to contain all items for combination
* @return List<List<T>> all combinations
*/
public static <T> List<List<T>> anm(List<T> all) {
return anm(all, all.size(), all.size());
}
/**
* Traverse A(n, m) combinations of a list
* @param all list to contain all items for combination
* @param n m of A(n, m)
* @param m n of A(n, m)
* @return List<List<T>> all combinations
*/
public static <T> List<List<T>> anm(List<T> all, int n, int m) {
List<List<T>> combs = new ArrayList<>();
anm(all, n, m, comb -> {
combs.add(comb);
return false;
});
return combs;
}
/**
* Traverse A(n, m) combinations of a list to find first matched
* result combination and call back with the result.
* @param all list to contain all items for combination
* @param n m of A(n, m)
* @param m n of A(n, m)
* @return true if matched any kind of items combination else false, the
* callback can always return false if you want to traverse all combinations
*/
public static <T> boolean anm(List<T> all, int n, int m,
Function<List<T>, Boolean> callback) {
return anm(all, n, m, null, callback);
}
/**
* Traverse A(n, m) combinations of a list to find first matched
* result combination and call back with the result.
* @param all list to contain all items for combination
* @param n m of A(n, m)
* @param m n of A(n, m)
* @param selected list to contain selected items
* @return true if matched any kind of items combination else false, the
* callback can always return false if you want to traverse all combinations
*/
private static <T> boolean anm(List<T> all, int n, int m,
List<Integer> selected,
Function<List<T>, Boolean> callback) {
assert n <= all.size();
assert m <= n;
if (selected == null) {
selected = new ArrayList<>(m);
}
if (m == 0) {
// All n items are selected
List<T> tmpResult = new ArrayList<>();
for (int i : selected) {
tmpResult.add(all.get(i));
}
return callback.apply(tmpResult);
}
for (int i = 0; i < all.size(); i++) {
if (selected.contains(i)) {
continue;
}
int index = selected.size();
selected.add(i);
// Select current item, continue to select A(m-1, n-1)
if (anm(all, n - 1, m - 1, selected, callback)) {
return true;
}
selected.remove(index);
assert selected.size() == index : selected;
}
return false;
}
}

View File

@ -0,0 +1,77 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.hugegraph.date.SafeDateFormat;
import com.google.common.collect.ImmutableMap;
public final class DateUtil {
public static final Date DATE_ZERO = new Date(0L);
private static final Map<String, String> VALID_DFS = ImmutableMap.of(
"^\\d{4}-\\d{1,2}-\\d{1,2}",
"yyyy-MM-dd",
"^\\d{4}-\\d{1,2}-\\d{1,2}\\s\\d{2}:\\d{2}:\\d{2}",
"yyyy-MM-dd HH:mm:ss",
"^\\d{4}-\\d{1,2}-\\d{1,2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{1,3}",
"yyyy-MM-dd HH:mm:ss.SSS"
);
private static final Map<String, SafeDateFormat> DATE_FORMATS = new ConcurrentHashMap<>();
public static Date parse(String value) {
for (Map.Entry<String, String> entry : VALID_DFS.entrySet()) {
if (value.matches(entry.getKey())) {
return parse(value, entry.getValue());
}
}
throw new IllegalArgumentException(String.format(
"Expected date format is: %s, but got '%s'", VALID_DFS.values(), value));
}
public static Date parse(String value, String df) {
SafeDateFormat dateFormat = getDateFormat(df);
return dateFormat.parse(value);
}
public static Date now() {
return new Date();
}
private static SafeDateFormat getDateFormat(String df) {
SafeDateFormat dateFormat = DATE_FORMATS.get(df);
if (dateFormat == null) {
dateFormat = new SafeDateFormat(df);
SafeDateFormat previous = DATE_FORMATS.putIfAbsent(df, dateFormat);
if (previous != null) {
dateFormat = previous;
}
}
return dateFormat;
}
public static Object toPattern(String df) {
SafeDateFormat dateFormat = getDateFormat(df);
return dateFormat.toPattern();
}
}

View File

@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.util.Collection;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
public final class E {
public static void checkNotNull(Object object, String elem) {
Preconditions.checkNotNull(object, "The '%s' can't be null", elem);
}
public static void checkNotNull(Object object, String elem, String owner) {
Preconditions.checkNotNull(object,
"The '%s' of '%s' can't be null",
elem, owner);
}
public static void checkNotEmpty(Collection<?> collection, String elem) {
Preconditions.checkArgument(!collection.isEmpty(),
"The '%s' can't be empty", elem);
}
public static void checkNotEmpty(Collection<?> collection, String elem, String owner) {
Preconditions.checkArgument(!collection.isEmpty(),
"The '%s' of '%s' can't be empty",
elem, owner);
}
public static void checkArgument(boolean expression,
@Nullable String message,
@Nullable Object... args) {
Preconditions.checkArgument(expression, message, args);
}
public static void checkArgumentNotNull(Object object,
@Nullable String message,
@Nullable Object... args) {
Preconditions.checkArgument(object != null, message, args);
}
public static void checkState(boolean expression,
@Nullable String message,
@Nullable Object... args) {
Preconditions.checkState(expression, message, args);
}
}

View File

@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public final class ExceptionUtil {
public static Throwable rootCause(Throwable e) {
Throwable cause = e;
while (cause.getCause() != null) {
cause = cause.getCause();
}
return cause;
}
public static RuntimeException transToRuntimeException(Throwable e) {
if (e instanceof RuntimeException) {
return (RuntimeException) e;
}
return new RuntimeException(rootCause(e).getMessage(), e);
}
public static <T> T futureGet(Future<T> future) {
try {
return future.get();
} catch (InterruptedException e) {
throw ExceptionUtil.transToRuntimeException(e);
} catch (ExecutionException e) {
throw ExceptionUtil.transToRuntimeException(e.getCause());
}
}
}

View File

@ -0,0 +1,111 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.hugegraph.concurrent.PausableScheduledThreadPool;
public final class ExecutorUtil {
public static ThreadPoolExecutor newDynamicThreadExecutor(String name,
int corePoolSize,
int maximumPoolSize) {
long keepAliveTime = 60L;
TimeUnit unit = TimeUnit.SECONDS;
ThreadFactory factory = new BasicThreadFactory.Builder()
.namingPattern(name)
.build();
CustomBlockingQueue<Runnable> workQueue = new CustomBlockingQueue<>();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
factory,
new ThreadPoolExecutor.CallerRunsPolicy()
);
workQueue.setThreadPoolExecutor(threadPoolExecutor);
return threadPoolExecutor;
}
static class CustomBlockingQueue<E> extends LinkedBlockingQueue<E> {
private ThreadPoolExecutor threadPoolExecutor;
public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) {
this.threadPoolExecutor = threadPoolExecutor;
}
@Override
public boolean offer(E e) {
if (threadPoolExecutor.getPoolSize() < threadPoolExecutor.getMaximumPoolSize()) {
return false;
}
return super.offer(e);
}
}
public static ExecutorService newFixedThreadPool(String name) {
return newFixedThreadPool(1, name);
}
public static ExecutorService newFixedThreadPool(int size, String name) {
ThreadFactory factory = new BasicThreadFactory.Builder()
.namingPattern(name)
.build();
return Executors.newFixedThreadPool(size, factory);
}
public static ScheduledExecutorService newScheduledThreadPool(String name) {
return newScheduledThreadPool(1, name);
}
public static ScheduledExecutorService newScheduledThreadPool(int size,
String name) {
ThreadFactory factory = new BasicThreadFactory.Builder()
.namingPattern(name)
.build();
return Executors.newScheduledThreadPool(size, factory);
}
public static PausableScheduledThreadPool newPausableScheduledThreadPool(
String name) {
return newPausableScheduledThreadPool(1, name);
}
public static PausableScheduledThreadPool newPausableScheduledThreadPool(
int size, String name) {
ThreadFactory factory = new BasicThreadFactory.Builder()
.namingPattern(name)
.build();
return new PausableScheduledThreadPool(size, factory);
}
}

View File

@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.nio.charset.Charset;
import com.google.common.base.Charsets;
import com.google.common.hash.Hashing;
public final class HashUtil {
private static final Charset CHARSET = Charsets.UTF_8;
public static byte[] hash(byte[] bytes) {
return Hashing.murmur3_32().hashBytes(bytes).asBytes();
}
public static String hash(String value) {
return Hashing.murmur3_32().hashString(value, CHARSET).toString();
}
public static byte[] hash128(byte[] bytes) {
return Hashing.murmur3_128().hashBytes(bytes).asBytes();
}
public static String hash128(String value) {
return Hashing.murmur3_128().hashString(value, CHARSET).toString();
}
}

View File

@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public final class InsertionOrderUtil {
public static <K, V> Map<K, V> newMap() {
return new LinkedHashMap<>();
}
public static <K, V> Map<K, V> newMap(int initialCapacity) {
return new LinkedHashMap<>(initialCapacity);
}
public static <K, V> Map<K, V> newMap(Map<K, V> origin) {
return new LinkedHashMap<>(origin);
}
public static <V> Set<V> newSet() {
return new LinkedHashSet<>();
}
public static <V> Set<V> newSet(int initialCapacity) {
return new LinkedHashSet<>(initialCapacity);
}
public static <V> Set<V> newSet(Set<V> origin) {
return new LinkedHashSet<>(origin);
}
public static <V> List<V> newList() {
return new ArrayList<>();
}
public static <V> List<V> newList(int initialCapacity) {
return new ArrayList<>(initialCapacity);
}
public static <V> List<V> newList(List<V> origin) {
return new ArrayList<>(origin);
}
}

View File

@ -0,0 +1,94 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.io.IOException;
import org.apache.hugegraph.rest.SerializeException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Utility class for JSON operations.
*/
public final class JsonUtilCommon {
/**
* ObjectMapper instance used for JSON operations.
*/
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* Registers a module with the ObjectMapper.
*
* @param module the module to register
*/
public static void registerModule(Module module) {
MAPPER.registerModule(module);
}
/**
* Converts an object to a JSON string.
*
* @param object the object to convert
* @return the JSON string representation of the object
* @throws SerializeException if the object cannot be serialized
*/
public static String toJson(Object object) {
try {
return MAPPER.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new SerializeException("Failed to serialize object '%s'", e, object);
}
}
/**
* Converts a JSON string to an object of the specified class.
*
* @param json the JSON string
* @param clazz the class of the object
* @return the object represented by the JSON string
* @throws SerializeException if the JSON string cannot be deserialized
*/
public static <T> T fromJson(String json, Class<T> clazz) {
try {
return MAPPER.readValue(json, clazz);
} catch (IOException e) {
throw new SerializeException("Failed to deserialize json '%s'", e, json);
}
}
/**
* Converts a JsonNode to an object of the specified class.
*
* @param node the JsonNode
* @param clazz the class of the object
* @return the object represented by the JsonNode
* @throws SerializeException if the JsonNode cannot be deserialized
*/
public static <T> T convertValue(JsonNode node, Class<T> clazz) {
try {
return MAPPER.convertValue(node, clazz);
} catch (IllegalArgumentException e) {
throw new SerializeException("Failed to deserialize json node '%s'", e, node);
}
}
}

View File

@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class Log {
public static Logger logger(String name) {
return LoggerFactory.getLogger(name);
}
public static Logger logger(Class<?> clazz) {
return LoggerFactory.getLogger(clazz);
}
}

View File

@ -0,0 +1,151 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
public final class LongEncoding {
private static final String B64_SYMBOLS =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";
private static final String LENGTH_SYMBOLS = "0123456789ABCDEF";
private static final char SORTABLE_NEG = LENGTH_SYMBOLS.charAt(0);
private static final char SIGNED_NEG = '-';
private static final long FULL_LONG = Long.MIN_VALUE;
public static String encodeNumber(Object number) {
Number num = NumericUtil.convertToNumber(number);
long value = NumericUtil.numberToSortableLong(num);
return encodeSortable(value);
}
public static Number decodeNumber(String str, Class<?> clazz) {
long value = decodeSortable(str);
return NumericUtil.sortableLongToNumber(value, clazz);
}
public static String encodeSortable(long num) {
boolean negative = false;
if (num < 0L) {
negative = true;
num += FULL_LONG;
}
String encoded = encode(num, B64_SYMBOLS);
int length = encoded.length();
E.checkArgument(length <= LENGTH_SYMBOLS.length(),
"Length symbols can't represent encoded number '%s'",
encoded);
StringBuilder sb = new StringBuilder(length + 2);
if (negative) {
sb.append(SORTABLE_NEG);
}
char len = LENGTH_SYMBOLS.charAt(length);
sb.append(len);
sb.append(encoded);
return sb.toString();
}
public static long decodeSortable(String str) {
E.checkArgument(str.length() >= 2,
"Length of sortable encoded string must be >=2");
boolean negative = str.charAt(0) == SORTABLE_NEG;
int lengthPos = 0;
if (negative) {
lengthPos = 1;
}
int length = B64_SYMBOLS.indexOf(str.charAt(lengthPos));
E.checkArgument(length == str.length() - lengthPos - 1,
"Can't decode illegal string '%s' with wrong length",
str);
String encoded = str.substring(lengthPos + 1);
long value = decode(encoded, B64_SYMBOLS);
if (negative) {
value -= FULL_LONG;
}
return value;
}
public static String encodeSignedB64(long value) {
boolean negative = false;
if (value < 0L) {
negative = true;
if (value == FULL_LONG) {
return "-80000000000";
}
value = -value;
}
assert value >= 0L : value;
String encoded = encodeB64(value);
return negative ? SIGNED_NEG + encoded : encoded;
}
public static long decodeSignedB64(String value) {
boolean negative = false;
if (!value.isEmpty() && value.charAt(0) == SIGNED_NEG) {
negative = true;
value = value.substring(1);
}
long decoded = decodeB64(value);
return negative ? -decoded : decoded;
}
public static boolean validB64Char(char c) {
return B64_SYMBOLS.indexOf(c) != -1;
}
public static String encodeB64(long num) {
return encode(num, B64_SYMBOLS);
}
public static long decodeB64(String str) {
return decode(str, B64_SYMBOLS);
}
public static long decode(String encoded, String symbols) {
final int B = symbols.length();
E.checkArgument(B > 0, "The symbols parameter can't be empty");
long num = 0L;
for (char ch : encoded.toCharArray()) {
num *= B;
int pos = symbols.indexOf(ch);
if (pos < 0) {
throw new NumberFormatException(String.format(
"Can't decode symbol '%s' in string '%s'",
ch, encoded));
}
num += pos;
}
return num;
}
public static String encode(long num, String symbols) {
final int B = symbols.length();
E.checkArgument(num >= 0L, "Expected non-negative number: %s", num);
E.checkArgument(B > 0, "The symbols parameter can't be empty");
StringBuilder sb = new StringBuilder();
do {
sb.append(symbols.charAt((int) (num % B)));
num /= B;
} while (num != 0L);
return sb.reverse().toString();
}
}

View File

@ -0,0 +1,372 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.function.Function;
/**
* This file is copied verbatim from Apache Lucene NumericUtils.java
* Only the double/float to sortable long/int conversions are retained.
*/
public final class NumericUtil {
private static final long FULL_LONG = Long.MIN_VALUE;
private static final int FULL_INT = Integer.MIN_VALUE;
private static final byte FULL_BYTE = Byte.MIN_VALUE;
private NumericUtil() {
}
/**
* Converts a <code>double</code> value to a sortable signed
* <code>long</code>. The value is converted by getting their IEEE 754
* floating-point &quot;double format&quot; bit layout and then some bits
* are swapped, to be able to compare the result as long. By this the
* precision is not reduced, but the value can be easily used as a long. The
* sort order (including {@link Double#NaN}) is defined by
* {@link Double#compareTo}; {@code NaN} is greater than positive infinity.
* @param value input double value
* @return output sortable long value
* @see #sortableLongToDouble
*/
public static long doubleToSortableLong(double value) {
return sortableDoubleBits(Double.doubleToLongBits(value));
}
/**
* Converts a sortable <code>long</code> back to a <code>double</code>.
* @param value input double value
* @return output sortable long value
* @see #doubleToSortableLong
*/
public static double sortableLongToDouble(long value) {
return Double.longBitsToDouble(sortableDoubleBits(value));
}
/**
* Converts a <code>float</code> value to a sortable signed
* <code>int</code>. The value is converted by getting their IEEE 754
* floating-point &quot;float format&quot; bit layout and then some bits are
* swapped, to be able to compare the result as int. By this the precision
* is not reduced, but the value can be easily used as an int. The sort order
* (including {@link Float#NaN}) is defined by {@link Float#compareTo};
* {@code NaN} is greater than positive infinity.
* @param value input float value
* @return output sortable int value
* @see #sortableIntToFloat
*/
public static int floatToSortableInt(float value) {
return sortableFloatBits(Float.floatToIntBits(value));
}
/**
* Converts a sortable <code>int</code> back to a <code>float</code>.
* @param value input int value
* @return output sortable float value
* @see #floatToSortableInt
*/
public static float sortableIntToFloat(int value) {
return Float.intBitsToFloat(sortableFloatBits(value));
}
/**
* Converts IEEE 754 representation of a double to sortable order (or back
* to the original)
* @param bits The long format of a double value
* @return The sortable long value
*/
public static long sortableDoubleBits(long bits) {
return bits ^ ((bits >> 63) & 0x7fffffffffffffffL);
}
/**
* Converts IEEE 754 representation of a float to sortable order (or back to
* the original)
* @param bits The int format of a float value
* @return The sortable int value
*/
public static int sortableFloatBits(int bits) {
/*
* Convert to its inverse digits if negative else keep the origin
* NOTE: (bits >> 31) is 0x00000000 if bits >= 0
* (bits >> 31) is 0xFFFFFFFF if bits < 0
*/
return bits ^ ((bits >> 31) & 0x7fffffff);
}
public static long numberToSortableLong(Number number) {
if (number instanceof Double) {
return doubleToSortableLong(number.doubleValue());
} else if (number instanceof Float) {
return floatToSortableInt(number.floatValue());
} else if (number instanceof Long || number instanceof Integer ||
number instanceof Short || number instanceof Byte) {
return number.longValue();
} else if (number instanceof BigDecimal) {
BigDecimal bd = (BigDecimal) number;
boolean intNumber = bd.stripTrailingZeros().scale() <= 0;
return intNumber ? bd.longValueExact() :
doubleToSortableLong(bd.doubleValue());
}
// TODO: support other number types
throw unsupportedNumberType(number);
}
public static Number sortableLongToNumber(long value, Class<?> clazz) {
assert NumericUtil.isNumber(clazz);
if (clazz == Double.class) {
return sortableLongToDouble(value);
} else if (clazz == Float.class) {
return sortableIntToFloat((int) value);
} else if (clazz == Long.class) {
return value;
} else if (clazz == Integer.class) {
return (int) value;
} else if (clazz == Short.class) {
return (short) value;
} else if (clazz == Byte.class) {
return (byte) value;
}
// TODO: support other number types
throw unsupportedNumberType(clazz);
}
public static byte[] numberToSortableBytes(Number number) {
if (number instanceof Long) {
return longToSortableBytes(number.longValue());
} else if (number instanceof Double) {
long value = doubleToSortableLong(number.doubleValue());
return longToSortableBytes(value);
} else if (number instanceof Float) {
int value = floatToSortableInt(number.floatValue());
return intToSortableBytes(value);
} else if (number instanceof Integer || number instanceof Short) {
return intToSortableBytes(number.intValue());
} else if (number instanceof Byte) {
return byteToSortableBytes(number.byteValue());
}
// TODO: support other number types
throw unsupportedNumberType(number);
}
public static Number sortableBytesToNumber(byte[] bytes, Class<?> clazz) {
assert NumericUtil.isNumber(clazz);
if (clazz == Long.class) {
return sortableBytesToLong(bytes);
} else if (clazz == Double.class) {
return sortableLongToDouble(sortableBytesToLong(bytes));
} else if (clazz == Float.class) {
return sortableIntToFloat(sortableBytesToInt(bytes));
} else if (clazz == Integer.class) {
return sortableBytesToInt(bytes);
} else if (clazz == Short.class) {
return (short) sortableBytesToInt(bytes);
} else if (clazz == Byte.class) {
return sortableBytesToByte(bytes);
}
// TODO: support other number types
throw unsupportedNumberType(clazz);
}
public static Number minValueOf(Class<?> clazz) {
E.checkArgumentNotNull(clazz, "The clazz can't be null");
if (Long.class.isAssignableFrom(clazz) ||
Double.class.isAssignableFrom(clazz)) {
return Long.MIN_VALUE;
}
if (Integer.class.isAssignableFrom(clazz) ||
Short.class.isAssignableFrom(clazz) ||
Float.class.isAssignableFrom(clazz)) {
return Integer.MIN_VALUE;
}
if (Byte.class.isAssignableFrom(clazz)) {
return Byte.MIN_VALUE;
}
// TODO: support other number types
throw unsupportedNumberType(clazz);
}
public static Number maxValueOf(Class<?> clazz) {
E.checkArgumentNotNull(clazz, "The clazz can't be null");
if (Long.class.isAssignableFrom(clazz) ||
Double.class.isAssignableFrom(clazz)) {
return Long.MAX_VALUE;
}
if (Integer.class.isAssignableFrom(clazz) ||
Float.class.isAssignableFrom(clazz) ||
Short.class.isAssignableFrom(clazz)) {
return Integer.MAX_VALUE;
}
if (Byte.class.isAssignableFrom(clazz)) {
return Byte.MAX_VALUE;
}
// TODO: support other number types
throw unsupportedNumberType(clazz);
}
public static byte[] longToSortableBytes(long value) {
return longToBytes(value + FULL_LONG);
}
public static long sortableBytesToLong(byte[] bytes) {
return bytesToLong(bytes) - FULL_LONG;
}
public static byte[] intToSortableBytes(int value) {
return intToBytes(value + FULL_INT);
}
public static int sortableBytesToInt(byte[] bytes) {
return bytesToInt(bytes) - FULL_INT;
}
public static byte[] byteToSortableBytes(byte value) {
value += FULL_BYTE;
return new byte[]{value};
}
public static byte sortableBytesToByte(byte[] bytes) {
assert bytes.length == 1;
byte value = bytes[0];
value -= FULL_BYTE;
return value;
}
public static byte[] longToBytes(long value) {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.putLong(value);
return buffer.array();
}
public static long bytesToLong(byte[] bytes) {
assert bytes.length == Long.BYTES;
return ByteBuffer.wrap(bytes).getLong();
}
public static byte[] intToBytes(int value) {
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
buffer.putInt(value);
return buffer.array();
}
public static int bytesToInt(byte[] bytes) {
assert bytes.length == Integer.BYTES;
return ByteBuffer.wrap(bytes).getInt();
}
public static boolean isNumber(Object value) {
if (value == null) {
return false;
}
return isNumber(value.getClass());
}
public static boolean isNumber(Class<?> clazz) {
if (clazz.isPrimitive()) {
return clazz == int.class || clazz == long.class ||
clazz == float.class || clazz == double.class ||
clazz == short.class || clazz == byte.class;
}
return Number.class.isAssignableFrom(clazz);
}
public static Number convertToNumber(Object value) {
if (value == null) {
return null;
}
Number number;
if (isNumber(value)) {
number = (Number) value;
} else {
if (value instanceof Date) {
number = ((Date) value).getTime();
} else {
// TODO: add some more types to convert
number = new BigDecimal(value.toString());
}
}
return number;
}
/**
* Compare object with a number, the object should be a number,
* or it can be converted to a BigDecimal
* @param first might be number or string
* @param second must be number
* @return 0 if first is numerically equal to second;
* a negative int if first is numerically less than second;
* a positive int if first is numerically greater than second.
*/
public static int compareNumber(Object first, Number second) {
if (first == null) {
E.checkArgument(first != null,
"The first parameter can't be null");
}
if (second == null) {
E.checkArgument(second != null,
"The second parameter can't be null");
}
if (first instanceof Number && first instanceof Comparable &&
first.getClass().equals(second.getClass())) {
@SuppressWarnings("unchecked")
Comparable<Number> cmpFirst = (Comparable<Number>) first;
return cmpFirst.compareTo(second);
}
Function<Object, BigDecimal> toBig = (number) -> {
try {
return new BigDecimal(number.toString());
} catch (NumberFormatException e) {
throw new IllegalArgumentException(String.format(
"Can't compare between '%s' and '%s', " +
"they must be numbers", first, second));
}
};
BigDecimal n1 = toBig.apply(first);
BigDecimal n2 = toBig.apply(second);
return n1.compareTo(n2);
}
private static IllegalArgumentException unsupportedNumberType(Class<?> c) {
return new IllegalArgumentException(String.format(
"Unsupported number type: %s", c.getSimpleName()));
}
private static IllegalArgumentException unsupportedNumberType(Number num) {
return new IllegalArgumentException(String.format(
"Unsupported number type: %s(%s)",
num.getClass().getSimpleName(), num));
}
}

View File

@ -0,0 +1,103 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import com.google.common.base.Functions;
import com.google.common.collect.Ordering;
public class OrderLimitMap<K extends Comparable<K>, V extends Comparable<V>> extends TreeMap<K, V> {
private static final long serialVersionUID = 756490437953358633L;
private final int capacity;
private final Map<K, V> valueMap;
private static <V extends Comparable<V>> Ordering<? super V> incr() {
return Ordering.from(Comparable::compareTo);
}
private static <V extends Comparable<V>> Ordering<? super V> decr() {
return Ordering.from((V o1, V o2) -> -o1.compareTo(o2));
}
public OrderLimitMap(int capacity) {
this(capacity, false);
}
public OrderLimitMap(int capacity, boolean incr) {
this(capacity, incr ? incr() : decr(), new HashMap<>());
}
private OrderLimitMap(int capacity, Ordering<? super V> ordering,
HashMap<K, V> valueMap) {
/*
* onResultOf: for getting the value for the key from value map
* compound: keep insertion order
*/
super(ordering.onResultOf(Functions.forMap(valueMap))
.compound(Ordering.natural()));
E.checkArgument(capacity > 0, "The capacity must be > 0");
this.capacity = capacity;
this.valueMap = valueMap;
}
@Override
public V put(K k, V v) {
if (this.valueMap.containsKey(k)) {
super.remove(k);
} else if (this.valueMap.size() >= this.capacity) {
K key = super.lastKey();
super.remove(key);
this.valueMap.remove(key);
}
this.valueMap.put(k, v);
return super.put(k, v);
}
@Override
public V get(Object key) {
return this.valueMap.get(key);
}
@Override
public V getOrDefault(Object key, V defaultValue) {
return this.valueMap.getOrDefault(key, defaultValue);
}
@Override
public boolean containsKey(Object key) {
return this.valueMap.containsKey(key);
}
public Map<K, V> topN(int n) {
E.checkArgument(n > 0, "'N' Must be positive, but got '%s'", n);
Map<K, V> top = InsertionOrderUtil.newMap();
int i = 0;
for (Map.Entry<K, V> entry : this.entrySet()) {
top.put(entry.getKey(), entry.getValue());
if (++i >= n) {
break;
}
}
return top;
}
}

View File

@ -0,0 +1,124 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.apache.hugegraph.iterator.ExtendableIterator;
import com.google.common.collect.Lists;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
public final class ReflectionUtil {
public static boolean isSimpleType(Class<?> type) {
return type.isPrimitive() ||
type.equals(String.class) ||
type.equals(Boolean.class) ||
type.equals(Character.class) ||
NumericUtil.isNumber(type);
}
public static List<Method> getMethodsAnnotatedWith(
Class<?> type,
Class<? extends Annotation> annotation,
boolean withSuperClass) {
final List<Method> methods = new LinkedList<>();
Class<?> klass = type;
do {
for (Method method : klass.getDeclaredMethods()) {
if (method.isAnnotationPresent(annotation)) {
methods.add(method);
}
}
klass = klass.getSuperclass();
} while (klass != Object.class && withSuperClass);
return methods;
}
public static List<CtMethod> getMethodsAnnotatedWith(
CtClass type,
Class<? extends Annotation> annotation,
boolean withSuperClass)
throws NotFoundException {
final List<CtMethod> methods = new LinkedList<>();
CtClass klass = type;
do {
for (CtMethod method : klass.getDeclaredMethods()) {
if (method.hasAnnotation(annotation)) {
methods.add(method);
}
}
klass = klass.getSuperclass();
} while (klass != null && withSuperClass);
return methods;
}
public static Iterator<ClassInfo> classes(String... packages)
throws IOException {
ClassPath path = ClassPath.from(ReflectionUtil.class.getClassLoader());
ExtendableIterator<ClassInfo> results = new ExtendableIterator<>();
for (String p : packages) {
results.extend(path.getTopLevelClassesRecursive(p).iterator());
}
return results;
}
public static List<String> superClasses(String clazz)
throws NotFoundException {
CtClass klass = ClassPool.getDefault().get(clazz);
CtClass parent = klass.getSuperclass();
List<String> results = new LinkedList<>();
while (parent != null) {
results.add(parent.getName());
parent = parent.getSuperclass();
}
return Lists.reverse(results);
}
public static List<String> nestedClasses(String clazz)
throws NotFoundException {
CtClass klass = ClassPool.getDefault().get(clazz);
List<String> results = new LinkedList<>();
for (CtClass nested : klass.getNestedClasses()) {
results.add(nested.getName());
}
return results;
}
public static String packageName(String clazz) {
int offset = clazz.lastIndexOf(".");
if (offset > 0) {
return clazz.substring(0, offset);
}
return "";
}
}

View File

@ -0,0 +1,123 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
public final class StringUtil {
public static Chars[] splitToCharsArray(String text, String delimiter) {
E.checkArgument(delimiter.length() > 0,
"The delimiter can't be empty");
Chars[] buffer = new Chars[text.length()];
int count = Chars.split(text, delimiter, buffer);
if (count == buffer.length) {
return buffer;
}
Chars[] result = new Chars[count];
System.arraycopy(buffer, 0, result, 0, count);
return result;
}
public static String[] split(String text, String delimiter) {
E.checkArgument(delimiter.length() > 0,
"The delimiter can't be empty");
Chars[] buffer = new Chars[text.length()];
int count = Chars.split(text, delimiter, buffer);
String[] result = new String[count];
for (int i = 0; i < count; i++) {
result[i] = buffer[i].toString();
}
return result;
}
public static class Chars implements CharSequence {
private final char[] chars;
private final int start;
private final int end;
public Chars(char[] chars, int start, int end) {
E.checkArgument(0 < start && start < chars.length || start == 0,
"Invalid start parameter %s", start);
E.checkArgument(start <= end && end <= chars.length,
"Invalid end parameter %s", end);
this.chars = chars;
this.start = start;
this.end = end;
}
@Override
public int length() {
return this.end - this.start;
}
@Override
public char charAt(int index) {
return this.chars[this.start + index];
}
@Override
public CharSequence subSequence(int start, int end) {
return new Chars(this.chars, this.start + start, this.start + end);
}
@Override
public boolean equals(Object object) {
if (!(object instanceof Chars)) {
return false;
}
Chars other = (Chars) object;
return this.toString().equals(other.toString());
}
@Override
public int hashCode() {
return this.toString().hashCode();
}
@Override
public String toString() {
return new String(this.chars, this.start, this.length());
}
public static Chars of(String string) {
return new Chars(string.toCharArray(), 0, string.length());
}
public static Chars[] of(String... strings) {
Chars[] results = new Chars[strings.length];
for (int i = 0; i < strings.length; i++) {
results[i] = Chars.of(strings[i]);
}
return results;
}
public static int split(String text, String delimiter, Chars[] buffer) {
int count = 0;
int from = 0;
char[] chars = text.toCharArray();
for (int to; (to = text.indexOf(delimiter, from)) >= 0;
from = to + delimiter.length()) {
buffer[count++] = new Chars(chars, from, to);
}
if (from < text.length()) {
buffer[count++] = new Chars(chars, from, text.length());
}
return count;
}
}
}

View File

@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.time.Duration;
import java.util.Date;
public final class TimeUtil {
@SuppressWarnings("deprecation")
public static long BASE_TIME = new Date(2017 - 1900, 10, 28).getTime();
public static long timeGen() {
return System.currentTimeMillis() - BASE_TIME;
}
public static long timeGen(Date date) {
return date.getTime() - BASE_TIME;
}
public static long timeGen(long time) {
return time - BASE_TIME;
}
public static long tillNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
public static String readableTime(long time) {
if (time > 60 * 1000) {
// Remove the milliseconds part
time = time / 1000 * 1000;
}
Duration duration = Duration.ofMillis(time);
return duration.toString()
.substring(2)
.replaceAll("(\\d[HMS])(?!$)", "$1 ")
.toLowerCase();
}
}

View File

@ -0,0 +1,181 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
public final class UnitUtil {
public static double bytesToMB(long bytes) {
return doubleWith2Scale(bytes / (double) Bytes.MB);
}
public static double bytesToGB(long bytes) {
return doubleWith2Scale(bytes / (double) Bytes.GB);
}
public static double doubleWith2Scale(double value) {
BigDecimal decimal = new BigDecimal(value);
return decimal.setScale(2, RoundingMode.HALF_UP).doubleValue();
}
public static String bytesToReadableString(long bytes) {
// NOTE: FileUtils.byteCountToDisplaySize() lost decimal precision
final String[] units = {"B", "KB", "MB", "GB", "TB", "PB", "EB"};
if (bytes <= 0L) {
return "0 B";
}
int i = (int) (Math.log(bytes) / Math.log(1024));
E.checkArgument(i < units.length,
"The bytes parameter is out of %s unit: %s",
units[units.length - 1], bytes);
double value = bytes / Math.pow(1024, i);
if (value % 1L == 0L) {
return ((long) value) + " " + units[i];
} else {
return doubleWith2Scale(value) + " " + units[i];
}
}
public static long bytesFromReadableString(String valueWithUnit) {
int spacePos = valueWithUnit.indexOf(" ");
E.checkArgument(spacePos >= 0,
"Invalid readable bytes '%s', " +
"expect format like '10 MB'", valueWithUnit);
String unit = valueWithUnit.substring(spacePos + 1);
long factor;
switch (unit.trim().toUpperCase()) {
case "B":
case "BYTE":
case "BYTES":
factor = 1L;
break;
case "KB":
case "KIB":
factor = Bytes.KB;
break;
case "MB":
case "MIB":
factor = Bytes.MB;
break;
case "GB":
case "GIB":
factor = Bytes.GB;
break;
case "TB":
case "TIB":
factor = Bytes.TB;
break;
case "PB":
case "PIB":
factor = Bytes.PB;
break;
case "EB":
case "EIB":
factor = Bytes.EB;
break;
default:
throw new IllegalArgumentException("Unrecognized unit " + unit);
}
double value;
try {
value = Double.parseDouble(valueWithUnit.substring(0, spacePos));
} catch (Exception e) {
throw new IllegalArgumentException(String.format(
"Invalid parameter(not number): '%s'", valueWithUnit), e);
}
value = value * factor;
E.checkArgument(value <= Long.MAX_VALUE,
"The value %s from parameter '%s' is out of range",
value, valueWithUnit);
return (long) value;
}
public static String timestampToReadableString(long time) {
Duration duration = Duration.ofMillis(time);
long days = duration.toDays();
long hours = duration.toHours();
long minutes = duration.toMinutes();
long seconds = duration.getSeconds();
if (days > 0) {
return String.format("%dd%dh%dm%ds",
days,
hours % 24,
minutes % 60,
seconds % 60);
} else if (hours > 0) {
return String.format("%dh%dm%ds",
hours,
minutes % 60,
seconds % 60);
} else if (minutes > 0) {
return String.format("%dm%ds",
minutes,
seconds % 60);
} else if (seconds > 0) {
long ms = duration.toMillis() % 1000L;
if (ms > 0L) {
return String.format("%ds%dms", seconds, ms);
} else {
return String.format("%ds", seconds);
}
} else {
return String.format("%dms", duration.toMillis());
}
}
public static long timestampFromReadableString(String valueWithUnit) {
long ms = 0L;
// Adapt format 'nDnHnMnS' to 'PnYnMnDTnHnMnS'
String formatDuration = valueWithUnit.toUpperCase();
if (formatDuration.indexOf('D') >= 0) {
// Contains days
assert !formatDuration.contains("MS");
formatDuration = "P" + formatDuration.replace("D", "DT");
} else {
// Not exists days
int msPos = formatDuration.indexOf("MS");
// If contains ms, remove the ms part
if (msPos >= 0) {
int sPos = formatDuration.indexOf("S");
if (0 <= sPos && sPos < msPos) {
// If contains second part
sPos += 1;
ms = Long.parseLong(formatDuration.substring(sPos, msPos));
ms %= 1000L;
formatDuration = formatDuration.substring(0, sPos);
} else {
// Not contains second part, only exists ms
ms = Long.parseLong(formatDuration.substring(0, msPos));
return ms;
}
} else {
assert formatDuration.endsWith("S");
}
formatDuration = "PT" + formatDuration;
}
Duration duration = Duration.parse(formatDuration);
return duration.toMillis() + ms;
}
}

View File

@ -0,0 +1,236 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Objects;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
public final class VersionUtil {
/**
* Compare if a version is inside a range [begin, end)
* @param version The version to be compared
* @param begin The lower bound of the range
* @param end The upper bound of the range
* @return true if belong to the range, otherwise false
*/
public static boolean match(Version version, String begin, String end) {
E.checkArgumentNotNull(version, "The version to match is null");
return version.compareTo(new Version(begin)) >= 0 &&
version.compareTo(new Version(end)) < 0;
}
/**
* Compare if a version is greater than the other one (inclusive)
* @param version The version to be compared
* @param other The lower bound of the range
* @return true if it's greater than the other, otherwise false
*/
public static boolean gte(String version, String other) {
E.checkArgumentNotNull(version, "The version to match is null");
return new Version(version).compareTo(new Version(other)) >= 0;
}
/**
* Check whether a component version is matched expected range,
* throw an exception if it's not matched.
* @param version The version to be checked
* @param begin The lower bound of the range
* @param end The upper bound of the range
* @param component The owner component of version
*/
public static void check(Version version, String begin, String end,
String component) {
E.checkState(VersionUtil.match(version, begin, end),
"The version %s of '%s' is not in [%s, %s)",
version, component, begin, end);
}
/**
* Get implementation version from manifest in jar
* @param clazz The class to be load from jar package
* @return The implementation version
*/
public static String getImplementationVersion(Class<?> clazz) {
/*
* We don't use Package.getImplementationVersion() due to
* a duplicate package would override the origin package info.
*/
String className = clazz.getSimpleName() + ".class";
String classPath = Objects.requireNonNull(clazz.getResource(className)).toString();
if (!classPath.startsWith("jar:file:")) {
// Class not from JAR
return null;
}
int offset = classPath.lastIndexOf("!");
assert offset > 0;
// Get manifest file path
String manifestPath = classPath.substring(0, offset + 1);
return getImplementationVersion(manifestPath);
}
public static String getImplementationVersion(String manifestPath) {
manifestPath += "/META-INF/MANIFEST.MF";
Manifest manifest;
try {
manifest = new Manifest(new URL(manifestPath).openStream());
} catch (IOException ignored) {
return null;
}
return manifest.getMainAttributes()
.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
}
/**
* Get version from pom.xml
* @return The pom version
*/
public static String getPomVersion() {
String cmd = "mvn help:evaluate -Dexpression=project.version " +
"-q -DforceStdout";
Process process = null;
InputStreamReader isr = null;
try {
process = Runtime.getRuntime().exec(cmd);
process.waitFor();
isr = new InputStreamReader(process.getInputStream());
BufferedReader br = new BufferedReader(isr);
return br.readLine();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (isr != null) {
try {
isr.close();
} catch (Exception ignored) {
// pass
}
}
// Destroy child process
if (process != null) {
process.destroy();
}
}
}
public static class Version implements Comparable<Version> {
public static Version of(Class<?> clazz) {
return Version.of(clazz, null);
}
public static Version of(Class<?> clazz, String defaultValue) {
String v = getImplementationVersion(clazz);
if (v == null) {
v = defaultValue;
}
return v == null ? null : new Version(v);
}
public static Version of(String version) {
return new Version(version);
}
/************************** Version define **************************/
private final String version;
private final int[] parts;
public Version(String version) {
E.checkArgumentNotNull(version, "The version is null");
E.checkArgument(version.matches("[0-9]+(\\.[0-9]+)*"),
"Invalid version format: %s", version);
this.version = version;
this.parts = parseVersion(version);
}
private static int[] parseVersion(String version) {
String[] parts = version.split("\\.");
int[] partsNumber = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
partsNumber[i] = Integer.parseInt(parts[i]);
}
return partsNumber;
}
public final String get() {
return this.version;
}
@Override
public int compareTo(Version that) {
if (that == null) {
return 1;
}
int[] thisParts = this.parts;
int[] thatParts = that.parts;
int length = Math.max(thisParts.length, thatParts.length);
for (int i = 0; i < length; i++) {
int thisPart = i < thisParts.length ? thisParts[i] : 0;
int thatPart = i < thatParts.length ? thatParts[i] : 0;
if (thisPart < thatPart) {
return -1;
}
if (thisPart > thatPart) {
return 1;
}
}
return 0;
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (this.getClass() != that.getClass()) {
return false;
}
return this.compareTo((Version) that) == 0;
}
@Override
public int hashCode() {
int hash = 0;
for (int i = this.parts.length - 1; i >= 0; i--) {
int part = this.parts[i];
if (part == 0 && hash == 0) {
continue;
}
hash = 31 * hash + Integer.hashCode(part);
}
return hash;
}
@Override
public String toString() {
return this.version;
}
}
}

View File

@ -0,0 +1,28 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.version;
import org.apache.hugegraph.util.VersionUtil.Version;
public class CommonVersion {
public static final String NAME = "hugegraph-common";
// The second parameter of Version.of() is for all-in-one JAR
public static final Version VERSION = Version.of(CommonVersion.class, "1.5.0");
}

View File

@ -0,0 +1,444 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.testutil;
import org.apache.hugegraph.unit.BaseUnitTest;
import org.junit.Test;
public class AssertTest extends BaseUnitTest {
@Test
public void testAssertEquals() {
Assert.assertEquals((byte) 1, Byte.valueOf("1"));
Assert.assertEquals((short) 1, Short.valueOf("1"));
Assert.assertEquals('1', Character.valueOf('1'));
Assert.assertEquals(1, Integer.valueOf("1"));
Assert.assertEquals(1L, Long.valueOf("1"));
Assert.assertEquals(1f, Float.valueOf("1"));
Assert.assertEquals(1d, Double.valueOf("1"));
}
@Test
public void testAssertEqualsWithError() {
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals((byte) 1, "1");
}, e -> {
Assert.assertContains("expected: java.lang.Byte",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals((short) 1, "1");
}, e -> {
Assert.assertContains("expected: java.lang.Short",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals('1', "1");
}, e -> {
Assert.assertContains("expected: java.lang.Character",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1, "1");
}, e -> {
Assert.assertContains("expected: java.lang.Integer",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1L, "1");
}, e -> {
Assert.assertContains("expected: java.lang.Long",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1f, "1.0");
}, e -> {
Assert.assertContains("expected: java.lang.Float",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1d, "1.0");
}, e -> {
Assert.assertContains("expected: java.lang.Double",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1f, "1");
}, e -> {
Assert.assertContains("expected:<1.0> but was:<1>",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1d, "1");
}, e -> {
Assert.assertContains("expected:<1.0> but was:<1>",
e.getMessage());
});
}
@Test
public void testAssertEqualsOfIntWithError() {
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1, (Byte) (byte) 1);
}, e -> {
Assert.assertContains("expected: java.lang.Integer",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1, (Short) (short) 1);
}, e -> {
Assert.assertContains("expected: java.lang.Integer",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1, (Character) '1');
}, e -> {
Assert.assertContains("expected: java.lang.Integer",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1, (Long) 1L);
}, e -> {
Assert.assertContains("expected: java.lang.Integer",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1, (Float) 1f);
}, e -> {
Assert.assertContains("expected:<1> but was:<1.0>",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1, (Double) 1d);
}, e -> {
Assert.assertContains("expected:<1> but was:<1.0>",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1, "1.0");
}, e -> {
Assert.assertContains("expected:<1> but was:<1.0>",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1, (Byte) (byte) 2);
}, e -> {
Assert.assertContains("expected:<1> but was:<2>",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertEquals(1, null);
}, e -> {
Assert.assertContains("expected:<1> but was:<null>",
e.getMessage());
});
}
@Test
public void testAssertThrows() {
Assert.assertThrows(NullPointerException.class, () -> {
throw new NullPointerException();
});
Assert.assertThrows(RuntimeException.class, () -> {
throw new RuntimeException();
});
Throwable exception = Assert.assertThrows(RuntimeException.class, () -> {
throw new RuntimeException("fake-error");
});
Assert.assertInstanceOf(RuntimeException.class, exception);
Assert.assertEquals("fake-error", exception.getMessage());
Assert.assertThrows(RuntimeException.class, () -> {
throw new RuntimeException("fake-error");
}, e -> {
Assert.assertEquals("fake-error", e.getMessage());
});
}
@Test
public void testAssertThrowsWithTypeError() {
try {
Assert.assertThrows(NullPointerException.class, () -> {
// pass
});
Assert.fail("Expect error");
} catch (AssertionError e) {
Assert.assertContains("java.lang.NullPointerException", e.getMessage());
}
try {
Assert.assertThrows(NullPointerException.class, () -> {
throw new RuntimeException();
});
Assert.fail("Expect error");
} catch (AssertionError e) {
Assert.assertContains("java.lang.NullPointerException", e.getMessage());
Assert.assertContains("java.lang.RuntimeException", e.getMessage());
}
}
@Test
public void testAssertThrowsWithMessageError() {
try {
Assert.assertThrows(RuntimeException.class, () -> {
throw new RuntimeException("fake-error");
}, e -> {
Assert.assertEquals("fake-error-typo", e.getMessage());
});
Assert.fail("Expect error");
} catch (AssertionError e) {
Assert.assertContains("expected:<fake-error[-typo]> but was:<fake-error[]>",
e.getMessage());
}
}
@Test
public void testAssertGt() {
Assert.assertGt((byte) 1, Byte.valueOf("2"));
Assert.assertGt((short) 1, Short.valueOf("2"));
Assert.assertGt(1, Integer.valueOf("2"));
Assert.assertGt(1L, Long.valueOf("2"));
Assert.assertGt(1f, Float.valueOf("1.01"));
Assert.assertGt(1d, Double.valueOf("1.01"));
Assert.assertGt((byte) 1, (byte) 2);
Assert.assertGt((short) 1, (short) 2);
Assert.assertGt(1, 2);
Assert.assertGt(1L, 2L);
Assert.assertGt(1f, 1.01f);
Assert.assertGt(1d, 1.01d);
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGt(1, 0);
}, e -> {
Assert.assertContains("Expected: a number > 1", e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGt(1, null);
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Integer",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGt(1, (byte) 2);
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Integer",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGt(1, 1.1);
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Integer",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGt(1, '2');
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Integer",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGt(0.9, 1);
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Double",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGt(0.9d, 0.98f);
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Double",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGt(0.9f, 0.98d);
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Float",
e.getMessage());
});
}
@Test
public void testAssertGte() {
Assert.assertGte((byte) 1, Byte.valueOf("2"));
Assert.assertGte((short) 1, Short.valueOf("2"));
Assert.assertGte(1, Integer.valueOf("2"));
Assert.assertGte(1L, Long.valueOf("2"));
Assert.assertGte(1f, Float.valueOf("1.01"));
Assert.assertGte(1d, Double.valueOf("1.01"));
Assert.assertGte((byte) 1, Byte.valueOf("1"));
Assert.assertGte((short) 1, Short.valueOf("1"));
Assert.assertGte(1, Integer.valueOf("1"));
Assert.assertGte(1L, Long.valueOf("1"));
Assert.assertGte(1f, Float.valueOf("1"));
Assert.assertGte(1d, Double.valueOf("1"));
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGte(1, 0);
}, e -> {
Assert.assertContains("Expected: a number >= 1", e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGte(1, 1.1);
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Integer",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGte(1, '2');
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Integer",
e.getMessage());
});
}
@Test
public void testAssertLt() {
Assert.assertLt((byte) 1, Byte.valueOf("0"));
Assert.assertLt((short) 1, Short.valueOf("0"));
Assert.assertLt(1, Integer.valueOf("0"));
Assert.assertLt(1L, Long.valueOf("0"));
Assert.assertLt(1f, Float.valueOf("0.99"));
Assert.assertLt(1d, Double.valueOf("0.99"));
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertLt(1, 2);
}, e -> {
Assert.assertContains("Expected: a number < 1", e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGt(1, 0.9);
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Integer",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertGt(1, '0');
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Integer",
e.getMessage());
});
}
@Test
public void testAssertLte() {
Assert.assertLte((byte) 1, Byte.valueOf("0"));
Assert.assertLte((short) 1, Short.valueOf("0"));
Assert.assertLte(1, Integer.valueOf("0"));
Assert.assertLte(1L, Long.valueOf("0"));
Assert.assertLte(1f, Float.valueOf("0.99"));
Assert.assertLte(1d, Double.valueOf("0.99"));
Assert.assertLte((byte) 1, Byte.valueOf("1"));
Assert.assertLte((short) 1, Short.valueOf("1"));
Assert.assertLte(1, Integer.valueOf("1"));
Assert.assertLte(1L, Long.valueOf("1"));
Assert.assertLte(1f, Float.valueOf("1"));
Assert.assertLte(1d, Double.valueOf("1"));
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertLte(1, 2);
}, e -> {
Assert.assertContains("Expected: a number <= 1", e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertLte(1, 0.9);
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Integer",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertLte(1, '0');
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Integer",
e.getMessage());
});
}
@Test
public void testAssertContains() {
Assert.assertContains("test", "test");
Assert.assertContains("test", "hellotest");
Assert.assertContains("test", "test123");
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertContains("test123", "test");
}, e -> {
Assert.assertContains("Expected: a string containing",
e.getMessage());
});
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertContains("null", null);
}, e -> {
Assert.assertContains("Expected: a string containing",
e.getMessage());
});
Assert.assertThrows(NullPointerException.class, () -> {
Assert.assertContains(null, "null");
}, e -> {
Assert.assertNull(e.getMessage());
});
}
@Test
public void testAssertInstanceOf() {
Assert.assertInstanceOf(Integer.class, 1);
Assert.assertInstanceOf(Double.class, 1.0);
Assert.assertInstanceOf(String.class, "1.0");
Assert.assertInstanceOf(BaseUnitTest.class, this);
Assert.assertThrows(AssertionError.class, () -> {
Assert.assertInstanceOf(Float.class, 1);
}, e -> {
Assert.assertContains("Expected: an instance of java.lang.Float",
e.getMessage());
});
}
}

View File

@ -0,0 +1,258 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.testutil;
import org.junit.Test;
public class WhiteboxTest {
@Test
public void testGetStaticInternalState() {
Assert.assertEquals(1, Whitebox.getInternalState(Test1.class,
"staticValue"));
Test1 test1 = newTest();
Assert.assertEquals(1, Whitebox.getInternalState(test1, "staticValue"));
Assert.assertEquals(2, Whitebox.getInternalState(test1,
"test2.staticValue"));
}
@Test
public void testSetStaticInternalState() {
try {
Whitebox.setInternalState(Test1.class, "staticValue", 11);
Assert.assertEquals(11, Test1.staticValue);
Test1 test1 = newTest();
Whitebox.setInternalState(test1, "staticValue", 111);
Assert.assertEquals(111, Test1.staticValue);
Whitebox.setInternalState(test1, "test2.staticValue", 22);
Assert.assertEquals(22, Test2.staticValue);
} finally {
Whitebox.setInternalState(Test1.class, "staticValue", 1);
Whitebox.setInternalState(Test2.class, "staticValue", 2);
}
}
@Test
public void testGetInternalState() {
Test1 test1 = newTest();
Assert.assertEquals(1, Whitebox.getInternalState(test1, "ivalue"));
Assert.assertEquals(2f, Whitebox.getInternalState(test1,
"test2.finalValue"));
Assert.assertEquals("3", Whitebox.getInternalState(test1,
"test2.test3.str"));
Assert.assertThrows(RuntimeException.class, () -> {
Whitebox.getInternalState(test1, "ivalue2");
});
Assert.assertThrows(RuntimeException.class, () -> {
Whitebox.getInternalState(test1, "test2.valueNotExist");
});
}
@Test
public void testSetInternalState() {
Test1 test1 = newTest();
Whitebox.setInternalState(test1, "ivalue", 11);
Assert.assertEquals(11, Whitebox.getInternalState(test1, "ivalue"));
Assert.assertEquals(11, test1.ivalue);
Whitebox.setInternalState(test1, "test2.finalValue", 22f);
Assert.assertEquals(22f, Whitebox.getInternalState(test1,
"test2.finalValue"));
Whitebox.setInternalState(test1, "test2.test3.str", "33");
Assert.assertEquals("33", Whitebox.getInternalState(test1,
"test2.test3.str"));
Assert.assertThrows(RuntimeException.class, () -> {
Whitebox.setInternalState(test1, "ivalue2", 11);
});
Assert.assertThrows(RuntimeException.class, () -> {
Whitebox.setInternalState(test1, "test2.valueNotExist", 22f);
});
Assert.assertThrows(RuntimeException.class, () -> {
Whitebox.setInternalState(test1, "test2.finalValue", 22d);
});
Assert.assertThrows(RuntimeException.class, () -> {
test1.test2 = null;
Whitebox.setInternalState(test1, "test2.finalValue", 22f);
}, e -> {
Assert.assertContains("Can't set value on null field",
e.getMessage());
});
}
@Test
public void testSetInternalFinalState() {
Test1 test1 = newTest();
Assert.assertEquals(1, test1.finalValue);
Whitebox.setInternalState(test1, "finalValue", 2);
Assert.assertEquals(2, Whitebox.getInternalState(test1, "finalValue"));
Whitebox.setInternalState(test1, "finalValue", 3);
Assert.assertEquals(3, Whitebox.getInternalState(test1, "finalValue"));
Whitebox.setInternalState(test1, "test2.finalValue", 22f);
Assert.assertEquals(22f, Whitebox.getInternalState(test1,
"test2.finalValue"));
// FIXME: seems don't take effect!!!
Assert.assertEquals(1, test1.finalValue);
Assert.assertEquals(2f, test1.test2.finalValue, 0f);
}
@Test
public void testInvokeStatic() {
Assert.assertEquals(1, Whitebox.invokeStatic(Test1.class, "svalue"));
Assert.assertEquals(2, Whitebox.invokeStatic(Test1.class, "svalue", 2));
Assert.assertEquals(2, Whitebox.invokeStatic(Test1.class, "svalue",
new Integer(2)));
Assert.assertEquals(2d, Whitebox.invokeStatic(Test1.class,
new Class[]{Object.class},
"svalue", 2d));
Assert.assertThrows(RuntimeException.class, () -> {
Whitebox.invokeStatic(Test1.class, "svalue2");
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
Whitebox.invokeStatic(Test1.class, "throwfunc1");
});
Assert.assertThrows(RuntimeException.class, () -> {
Whitebox.invokeStatic(Test1.class, "throwfunc2");
});
}
@Test
public void testInvoke() {
Test1 test1 = newTest();
Assert.assertEquals(1, Whitebox.invoke(test1.getClass(),
"value", test1));
Assert.assertEquals(3, Whitebox.invoke(test1.getClass(),
"addValue", test1, 2));
Assert.assertEquals(2f, Whitebox.invoke(test1, "test2", "value"));
Assert.assertEquals(2, Whitebox.invoke(test1, "test2",
new Class[]{Object.class},
"value", 2));
Assert.assertEquals(3, Whitebox.invoke(test1, "test4", "addValue", 2));
Assert.assertEquals(4, Whitebox.invoke(test1, "test4", "value"));
Assert.assertThrows(RuntimeException.class, () -> {
Whitebox.invoke(test1.getClass(), "value2", test1);
});
Assert.assertThrows(RuntimeException.class, () -> {
Whitebox.invoke(test1, "test22", "value");
});
Assert.assertThrows(RuntimeException.class, () -> {
Whitebox.invoke(test1, "test2", "value", 2);
});
Assert.assertThrows(RuntimeException.class, () -> {
Whitebox.invoke(test1.getClass(), "addValue", test1, 2.0);
});
}
private static Test1 newTest() {
Test1 test1 = new Test1();
test1.test2 = new Test2();
test1.test2.test3 = new Test3();
test1.test4 = new TestSubClass();
return test1;
}
@SuppressWarnings("unused")
private static class Test1 {
private static int staticValue = 1;
private int ivalue = 1;
private final int finalValue = 1;
private Test2 test2;
private TestSubClass test4;
private int value() {
return this.ivalue;
}
private int addValue(int i) {
return this.ivalue + i;
}
private static int svalue() {
return 1;
}
private static int svalue(int i) {
return i;
}
private static <T> T svalue(T o) {
return o;
}
private static int throwfunc1() {
throw new IllegalArgumentException("fake runtime exception");
}
private static int throwfunc2() throws Exception {
throw new Exception("fake exception");
}
}
@SuppressWarnings("unused")
private static class Test2 {
private static int staticValue = 2;
private final float finalValue = 2f;
private Test3 test3;
private float value() {
return this.finalValue;
}
private <T> T value(T o) {
return o;
}
}
@SuppressWarnings("unused")
private static class Test3 {
private String str = "3";
private String value() {
return this.str;
}
}
@SuppressWarnings("unused")
private static class TestSubClass extends Test1 {
private int value() {
return 4;
}
}
}

View File

@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.unit;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.commons.io.FileUtils;
import org.apache.hugegraph.util.ExceptionUtil;
import org.apache.hugegraph.util.TimeUtil;
import org.junit.AfterClass;
import org.junit.BeforeClass;
public class BaseUnitTest {
@BeforeClass
public static void init() {
// pass
}
@AfterClass
public static void clear() throws Exception {
// pass
}
protected static void runWithThreads(int threads, Runnable task) {
ExecutorService executor = Executors.newFixedThreadPool(threads);
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < threads; i++) {
futures.add(executor.submit(task));
}
for (Future<?> future : futures) {
ExceptionUtil.futureGet(future);
}
}
protected static void waitTillNext(long seconds) {
TimeUtil.tillNextMillis(TimeUtil.timeGen() + seconds * 1000);
}
public static void downloadFileByUrl(String url, String destPath) {
int connectTimeout = 5000;
int readTimeout = 5000;
try {
FileUtils.copyURLToFile(new URL(url), new File(destPath), connectTimeout, readTimeout);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,125 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.unit;
import org.apache.hugegraph.testutil.AssertTest;
import org.apache.hugegraph.testutil.WhiteboxTest;
import org.apache.hugegraph.unit.config.HugeConfigTest;
import org.apache.hugegraph.unit.config.OptionSpaceTest;
import org.apache.hugegraph.unit.event.EventHubTest;
import org.apache.hugegraph.unit.version.VersionTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.apache.hugegraph.unit.concurrent.AtomicLockTest;
import org.apache.hugegraph.unit.concurrent.BarrierEventTest;
import org.apache.hugegraph.unit.concurrent.KeyLockTest;
import org.apache.hugegraph.unit.concurrent.LockGroupTest;
import org.apache.hugegraph.unit.concurrent.LockManagerTest;
import org.apache.hugegraph.unit.concurrent.PausableScheduledThreadPoolTest;
import org.apache.hugegraph.unit.concurrent.RowLockTest;
import org.apache.hugegraph.unit.date.SafeDateFormatTest;
import org.apache.hugegraph.unit.iterator.BatchMapperIteratorTest;
import org.apache.hugegraph.unit.iterator.ExtendableIteratorTest;
import org.apache.hugegraph.unit.iterator.FilterIteratorTest;
import org.apache.hugegraph.unit.iterator.FlatMapperFilterIteratorTest;
import org.apache.hugegraph.unit.iterator.FlatMapperIteratorTest;
import org.apache.hugegraph.unit.iterator.LimitIteratorTest;
import org.apache.hugegraph.unit.iterator.ListIteratorTest;
import org.apache.hugegraph.unit.iterator.MapperIteratorTest;
import org.apache.hugegraph.unit.license.LicenseExtraParamTest;
import org.apache.hugegraph.unit.license.LicenseCreateParamTest;
import org.apache.hugegraph.unit.license.LicenseInstallParamTest;
import org.apache.hugegraph.unit.license.LicenseParamsTest;
import org.apache.hugegraph.unit.license.MachineInfoTest;
import org.apache.hugegraph.unit.perf.PerfUtilTest;
import org.apache.hugegraph.unit.perf.StopwatchTest;
import org.apache.hugegraph.unit.rest.RestClientTest;
import org.apache.hugegraph.unit.rest.RestResultTest;
import org.apache.hugegraph.unit.util.BytesTest;
import org.apache.hugegraph.unit.util.CollectionUtilTest;
import org.apache.hugegraph.unit.util.DateUtilTest;
import org.apache.hugegraph.unit.util.EcheckTest;
import org.apache.hugegraph.unit.util.HashUtilTest;
import org.apache.hugegraph.unit.util.InsertionOrderUtilTest;
import org.apache.hugegraph.unit.util.LogTest;
import org.apache.hugegraph.unit.util.LongEncodingTest;
import org.apache.hugegraph.unit.util.NumericUtilTest;
import org.apache.hugegraph.unit.util.OrderLimitMapTest;
import org.apache.hugegraph.unit.util.ReflectionUtilTest;
import org.apache.hugegraph.unit.util.StringUtilTest;
import org.apache.hugegraph.unit.util.TimeUtilTest;
import org.apache.hugegraph.unit.util.UnitUtilTest;
import org.apache.hugegraph.unit.util.VersionUtilTest;
@RunWith(Suite.class)
@Suite.SuiteClasses({
LockManagerTest.class,
LockGroupTest.class,
AtomicLockTest.class,
KeyLockTest.class,
RowLockTest.class,
PausableScheduledThreadPoolTest.class,
HugeConfigTest.class,
OptionSpaceTest.class,
SafeDateFormatTest.class,
BarrierEventTest.class,
EventHubTest.class,
PerfUtilTest.class,
StopwatchTest.class,
RestClientTest.class,
RestResultTest.class,
VersionTest.class,
ExtendableIteratorTest.class,
FilterIteratorTest.class,
LimitIteratorTest.class,
MapperIteratorTest.class,
FlatMapperIteratorTest.class,
FlatMapperFilterIteratorTest.class,
ListIteratorTest.class,
BatchMapperIteratorTest.class,
BytesTest.class,
CollectionUtilTest.class,
EcheckTest.class,
HashUtilTest.class,
InsertionOrderUtilTest.class,
LogTest.class,
NumericUtilTest.class,
ReflectionUtilTest.class,
StringUtilTest.class,
TimeUtilTest.class,
VersionUtilTest.class,
LongEncodingTest.class,
OrderLimitMapTest.class,
DateUtilTest.class,
UnitUtilTest.class,
LicenseExtraParamTest.class,
LicenseCreateParamTest.class,
LicenseInstallParamTest.class,
LicenseParamsTest.class,
MachineInfoTest.class,
AssertTest.class,
WhiteboxTest.class
})
public class UnitTestSuite {
}

View File

@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.unit.concurrent;
import org.junit.Test;
import org.apache.hugegraph.concurrent.AtomicLock;
import org.apache.hugegraph.testutil.Assert;
import org.apache.hugegraph.unit.BaseUnitTest;
public class AtomicLockTest extends BaseUnitTest {
@Test
public void testLockUnlock() {
AtomicLock lock = new AtomicLock("lock");
Assert.assertEquals("lock", lock.name());
Assert.assertTrue(lock.lock(0));
try {
Assert.assertFalse(lock.lock(1));
// lock in other threads
runWithThreads(2, () -> {
Assert.assertFalse(lock.tryLock());
});
lock.unlock();
} finally {
lock.unlock();
// unlock multi times is OK
lock.unlock();
lock.unlock();
}
Assert.assertThrows(RuntimeException.class, () -> {
lock.lock(-1);
}, e -> {
Assert.assertContains("Locking retry times should be in [0, 10], " +
"but got -1", e.getMessage());
});
Assert.assertThrows(RuntimeException.class, () -> {
lock.lock(11);
}, e -> {
Assert.assertContains("Locking retry times should be in [0, 10], " +
"but got 11", e.getMessage());
});
}
}

View File

@ -0,0 +1,288 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.unit.concurrent;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import org.apache.hugegraph.concurrent.BarrierEvent;
import org.apache.hugegraph.testutil.Assert;
public class BarrierEventTest {
private static final int WAIT_THREADS_COUNT = 10;
@Test(timeout = 5000)
public void testAWait() throws InterruptedException {
BarrierEvent barrierEvent = new BarrierEvent();
AtomicInteger result = new AtomicInteger(0);
CountDownLatch latch = new CountDownLatch(2);
Thread awaitThread = new Thread(() -> {
try {
barrierEvent.await();
result.incrementAndGet();
} catch (InterruptedException e) {
// Do nothing.
} finally {
latch.countDown();
}
});
awaitThread.start();
Thread signalThread = new Thread(() -> {
barrierEvent.signalAll();
latch.countDown();
});
signalThread.start();
latch.await();
Assert.assertEquals(1, result.get());
}
@Test
public void testAWaitWithTimeout() throws InterruptedException {
BarrierEvent barrierEvent = new BarrierEvent();
boolean signaled = barrierEvent.await(1L);
Assert.assertFalse(signaled);
}
@Test
public void testReset() throws InterruptedException {
BarrierEvent barrierEvent = new BarrierEvent();
boolean signaled = barrierEvent.await(1L);
Assert.assertFalse(signaled);
barrierEvent.signal();
signaled = barrierEvent.await(1L);
Assert.assertTrue(signaled);
barrierEvent.reset();
signaled = barrierEvent.await(1L);
Assert.assertFalse(signaled);
}
@Test
public void testSignal() throws InterruptedException {
BarrierEvent barrierEvent = new BarrierEvent();
boolean signaled = barrierEvent.await(1L);
Assert.assertFalse(signaled);
barrierEvent.signal();
signaled = barrierEvent.await(1L);
Assert.assertTrue(signaled);
}
@Test
public void testSignalByMultiThreadWithSignalFirst()
throws InterruptedException {
BarrierEvent barrierEvent = new BarrierEvent();
AtomicInteger eventCount = new AtomicInteger(0);
AtomicInteger waitThreadInterruptedCount = new AtomicInteger(0);
ExecutorService executorService =
Executors.newFixedThreadPool(WAIT_THREADS_COUNT + 1);
CountDownLatch waitLatch = new CountDownLatch(WAIT_THREADS_COUNT);
CountDownLatch signalLatch = new CountDownLatch(1);
for (int i = 0; i < WAIT_THREADS_COUNT; i++) {
executorService.submit(() -> {
try {
signalLatch.await();
barrierEvent.await();
eventCount.incrementAndGet();
} catch (InterruptedException e) {
waitThreadInterruptedCount.incrementAndGet();
} finally {
waitLatch.countDown();
}
});
}
executorService.submit(() -> {
barrierEvent.signal();
signalLatch.countDown();
});
executorService.shutdown();
executorService.awaitTermination(2L, TimeUnit.SECONDS);
waitLatch.await();
Assert.assertEquals(10, eventCount.get());
Assert.assertEquals(0, waitThreadInterruptedCount.get());
}
@Test
public void testSignalByMultiThreadWithSignalLast()
throws InterruptedException {
BarrierEvent barrierEvent = new BarrierEvent();
AtomicInteger eventCount = new AtomicInteger(0);
AtomicInteger waitThreadInterruptedCount = new AtomicInteger(0);
AtomicInteger signalThreadInterruptedCount = new AtomicInteger(0);
ExecutorService executorService =
Executors.newFixedThreadPool(WAIT_THREADS_COUNT + 1);
CountDownLatch waitLatch = new CountDownLatch(WAIT_THREADS_COUNT);
CountDownLatch signalLatch = new CountDownLatch(1);
for (int i = 0; i < WAIT_THREADS_COUNT; i++) {
executorService.submit(() -> {
try {
waitLatch.countDown();
barrierEvent.await();
eventCount.incrementAndGet();
} catch (InterruptedException e) {
waitThreadInterruptedCount.incrementAndGet();
}
});
}
executorService.submit(() -> {
try {
waitLatch.await();
} catch (InterruptedException e) {
signalThreadInterruptedCount.incrementAndGet();
}
barrierEvent.signal();
signalLatch.countDown();
});
signalLatch.await();
executorService.shutdownNow();
executorService.awaitTermination(1L, TimeUnit.SECONDS);
Assert.assertEquals(1, eventCount.get());
Assert.assertEquals(WAIT_THREADS_COUNT - 1,
waitThreadInterruptedCount.get());
Assert.assertEquals(0, signalThreadInterruptedCount.get());
}
@Test
public void testSignalAll() throws InterruptedException {
BarrierEvent barrierEvent = new BarrierEvent();
boolean signaled = barrierEvent.await(1L);
Assert.assertFalse(signaled);
barrierEvent.signalAll();
signaled = barrierEvent.await(1L);
Assert.assertTrue(signaled);
}
@Test
public void testSignalAllByMultiThreadWithSignalFirst()
throws InterruptedException {
BarrierEvent barrierEvent = new BarrierEvent();
AtomicInteger eventCount = new AtomicInteger(0);
AtomicInteger waitThreadInterruptedCount = new AtomicInteger(0);
ExecutorService executorService =
Executors.newFixedThreadPool(WAIT_THREADS_COUNT + 1);
CountDownLatch waitLatch = new CountDownLatch(WAIT_THREADS_COUNT);
CountDownLatch signalLatch = new CountDownLatch(1);
for (int i = 0; i < WAIT_THREADS_COUNT; i++) {
executorService.submit(() -> {
try {
signalLatch.await();
waitLatch.countDown();
barrierEvent.await();
eventCount.incrementAndGet();
} catch (InterruptedException e) {
waitThreadInterruptedCount.incrementAndGet();
}
});
}
executorService.submit(() -> {
barrierEvent.signalAll();
signalLatch.countDown();
});
executorService.shutdown();
executorService.awaitTermination(1L, TimeUnit.SECONDS);
Assert.assertEquals(10, eventCount.get());
Assert.assertEquals(0, waitThreadInterruptedCount.get());
}
@Test
public void testSignalAllByMultiThreadWithSignalLast()
throws InterruptedException {
BarrierEvent barrierEvent = new BarrierEvent();
AtomicInteger eventCount = new AtomicInteger(0);
AtomicInteger waitThreadInterruptedCount = new AtomicInteger(0);
AtomicInteger signalThreadInterruptedCount = new AtomicInteger(0);
ExecutorService executorService =
Executors.newFixedThreadPool(WAIT_THREADS_COUNT + 1);
CountDownLatch waitLatch = new CountDownLatch(WAIT_THREADS_COUNT);
CountDownLatch signalLatch = new CountDownLatch(1);
for (int i = 0; i < WAIT_THREADS_COUNT; i++) {
executorService.submit(() -> {
try {
waitLatch.countDown();
barrierEvent.await();
eventCount.incrementAndGet();
} catch (InterruptedException e) {
waitThreadInterruptedCount.incrementAndGet();
}
});
}
executorService.submit(() -> {
try {
waitLatch.await();
} catch (InterruptedException e) {
signalThreadInterruptedCount.incrementAndGet();
}
barrierEvent.signalAll();
signalLatch.countDown();
});
signalLatch.await();
executorService.shutdown();
executorService.awaitTermination(1L, TimeUnit.SECONDS);
Assert.assertEquals(WAIT_THREADS_COUNT, eventCount.get());
Assert.assertEquals(0, waitThreadInterruptedCount.get());
Assert.assertEquals(0, signalThreadInterruptedCount.get());
}
@Test
public void testSignalAllByMultiThreadWithSignalAwaitConcurrent()
throws InterruptedException {
BarrierEvent barrierEvent = new BarrierEvent();
AtomicInteger eventCount = new AtomicInteger(0);
AtomicInteger waitThreadInterruptedCount = new AtomicInteger(0);
AtomicInteger signalThreadInterruptedCount = new AtomicInteger(0);
ExecutorService executorService =
Executors.newFixedThreadPool(WAIT_THREADS_COUNT + 1);
CountDownLatch syncLatch = new CountDownLatch(1);
for (int i = 0; i < WAIT_THREADS_COUNT; i++) {
executorService.submit(() -> {
try {
syncLatch.await();
barrierEvent.await();
eventCount.incrementAndGet();
} catch (InterruptedException e) {
waitThreadInterruptedCount.incrementAndGet();
}
});
}
executorService.submit(() -> {
try {
syncLatch.await();
} catch (InterruptedException e) {
signalThreadInterruptedCount.incrementAndGet();
}
barrierEvent.signalAll();
});
syncLatch.countDown();
executorService.shutdown();
executorService.awaitTermination(1L, TimeUnit.SECONDS);
Assert.assertEquals(WAIT_THREADS_COUNT, eventCount.get());
Assert.assertEquals(0, waitThreadInterruptedCount.get());
Assert.assertEquals(0, signalThreadInterruptedCount.get());
}
}

View File

@ -0,0 +1,124 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.unit.concurrent;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.junit.Test;
import org.apache.hugegraph.concurrent.KeyLock;
import org.apache.hugegraph.testutil.Assert;
import org.apache.hugegraph.unit.BaseUnitTest;
public class KeyLockTest extends BaseUnitTest {
@Test
public void testLockUnlock() {
KeyLock locks = new KeyLock();
locks.lock("1");
try {
// lock again is OK
locks.lock("1");
// lock in other threads
runWithThreads(1, () -> {
locks.lock("2");
});
locks.unlock("1");
} finally {
locks.unlock("1");
}
Assert.assertThrows(IllegalMonitorStateException.class, () -> {
locks.unlock("2");
});
Assert.assertThrows(IllegalMonitorStateException.class, () -> {
locks.unlock("3");
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
locks.lock(null);
}, e -> {
Assert.assertContains("Lock key can't be null", e.getMessage());
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
locks.unlock(null);
}, e -> {
Assert.assertContains("Unlock key can't be null", e.getMessage());
});
}
@Test
public void testLockUnlockAll() {
KeyLock locks = new KeyLock();
List<Lock> ls = locks.lockAll("1", 2);
locks.unlockAll(ls);
runWithThreads(1, () -> {
List<Lock> ls2 = locks.lockAll("1", 3);
locks.unlockAll(ls2);
});
List<Lock> ls3 = locks.lockAll("1", 2, 3);
locks.unlockAll(ls3);
Assert.assertThrows(IllegalArgumentException.class, () -> {
locks.lockAll("1", null);
}, e -> {
Assert.assertContains("Lock key can't be null", e.getMessage());
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
locks.lockAll(null, "1");
}, e -> {
Assert.assertContains("Lock key can't be null", e.getMessage());
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
locks.lockAll(Arrays.asList("1", null, 2).toArray());
}, e -> {
Assert.assertContains("Lock key can't be null", e.getMessage());
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
locks.lockAll(Collections.emptyList().toArray());
}, e -> {
Assert.assertContains("Lock keys can't be null or empty",
e.getMessage());
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
locks.lockAll((Object[]) null);
}, e -> {
Assert.assertContains("Lock keys can't be null or empty",
e.getMessage());
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
locks.unlockAll(null);
}, e -> {
Assert.assertContains("Unlock locks can't be null", e.getMessage());
});
}
}

View File

@ -0,0 +1,106 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.unit.concurrent;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.junit.Test;
import org.apache.hugegraph.concurrent.AtomicLock;
import org.apache.hugegraph.concurrent.KeyLock;
import org.apache.hugegraph.concurrent.LockGroup;
import org.apache.hugegraph.concurrent.RowLock;
import org.apache.hugegraph.testutil.Assert;
import org.apache.hugegraph.unit.BaseUnitTest;
public class LockGroupTest extends BaseUnitTest {
private static final String GROUP = "LockGroupTest-test-group";
private final LockGroup group = new LockGroup(GROUP);
@Test
public void testLock() {
Lock lock = this.group.lock("lock");
Assert.assertTrue(lock instanceof ReentrantLock);
Lock lock1 = this.group.lock("lock");
Assert.assertSame(lock, lock1);
lock1.lock();
try {
// lock again is OK
lock1.lock();
// lock in other threads
runWithThreads(2, () -> {
Assert.assertFalse(lock1.tryLock());
});
lock1.unlock();
} finally {
lock1.unlock();
}
}
@Test
public void testAtomicLock() {
AtomicLock lock = this.group.atomicLock("lock");
Assert.assertNotNull(lock);
AtomicLock lock1 = this.group.atomicLock("lock");
Assert.assertSame(lock, lock1);
Assert.assertEquals("lock", lock1.name());
}
@Test
public void testReadWriteLock() {
ReadWriteLock lock = this.group.readWriteLock("lock");
Assert.assertTrue(lock instanceof ReentrantReadWriteLock);
ReadWriteLock lock1 = this.group.readWriteLock("lock");
Assert.assertSame(lock, lock1);
}
@Test
public void testKeyLock() {
KeyLock lock = this.group.keyLock("lock");
Assert.assertNotNull(lock);
KeyLock lock1 = this.group.keyLock("lock");
Assert.assertSame(lock, lock1);
}
@Test
public void testKeyLockWithSize() {
KeyLock lock = this.group.keyLock("lock", 10);
Assert.assertNotNull(lock);
KeyLock lock1 = this.group.keyLock("lock");
Assert.assertSame(lock, lock1);
}
@Test
public void testRowLock() {
RowLock<?> lock = this.group.rowLock("lock");
Assert.assertNotNull(lock);
RowLock<?> lock1 = this.group.rowLock("lock");
Assert.assertSame(lock, lock1);
}
@Test
public void testName() {
Assert.assertEquals(GROUP, this.group.name());
}
}

View File

@ -0,0 +1,125 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.unit.concurrent;
import org.junit.After;
import org.junit.Test;
import org.apache.hugegraph.concurrent.LockGroup;
import org.apache.hugegraph.concurrent.LockManager;
import org.apache.hugegraph.testutil.Assert;
import org.apache.hugegraph.unit.BaseUnitTest;
public class LockManagerTest extends BaseUnitTest {
private static final String GROUP = "LockManagerTest-test-group";
private static final String GROUP2 = GROUP + 2;
@After
public void teardown() {
LockManager manager = LockManager.instance();
if (manager.exists(GROUP)) {
manager.destroy(GROUP);
}
if (manager.exists(GROUP2)) {
manager.destroy(GROUP2);
}
}
@Test
public void testCreate() {
LockManager manager = LockManager.instance();
LockGroup lockGroup = manager.create(GROUP);
Assert.assertNotNull(lockGroup);
Assert.assertEquals(GROUP, lockGroup.name());
Assert.assertTrue(manager.exists(GROUP));
Assert.assertFalse(manager.exists(GROUP2));
LockGroup lockGroup2 = manager.create(GROUP2);
Assert.assertNotNull(lockGroup2);
Assert.assertEquals(GROUP2, lockGroup2.name());
Assert.assertTrue(manager.exists(GROUP2));
Assert.assertThrows(RuntimeException.class, () -> {
manager.create(GROUP);
}, e -> {
Assert.assertContains("LockGroup 'LockManagerTest-test-group' " +
"already exists", e.getMessage());
});
}
@Test
public void testGet() {
LockManager manager = LockManager.instance();
LockGroup lockGroup = manager.create(GROUP);
LockGroup lockGroup2 = manager.create(GROUP2);
Assert.assertSame(lockGroup, manager.get(GROUP));
Assert.assertSame(lockGroup2, manager.get(GROUP2));
Assert.assertSame(lockGroup, manager.get(GROUP));
Assert.assertSame(lockGroup2, manager.get(GROUP2));
Assert.assertThrows(RuntimeException.class, () -> {
manager.get("fake-lock-group");
}, e -> {
Assert.assertContains("LockGroup 'fake-lock-group' " +
"does not exists", e.getMessage());
});
}
@Test
public void testDestroy() {
LockManager manager = LockManager.instance();
LockGroup lockGroup = manager.create(GROUP);
LockGroup lockGroup2 = manager.create(GROUP2);
Assert.assertTrue(manager.exists(GROUP));
Assert.assertTrue(manager.exists(GROUP2));
Assert.assertSame(lockGroup, manager.get(GROUP));
Assert.assertSame(lockGroup2, manager.get(GROUP2));
manager.destroy(GROUP);
Assert.assertFalse(manager.exists(GROUP));
Assert.assertTrue(manager.exists(GROUP2));
Assert.assertThrows(RuntimeException.class, () -> {
manager.get(GROUP);
}, e -> {
Assert.assertContains("does not exists", e.getMessage());
});
Assert.assertSame(lockGroup2, manager.get(GROUP2));
manager.destroy(GROUP2);
Assert.assertFalse(manager.exists(GROUP));
Assert.assertFalse(manager.exists(GROUP2));
Assert.assertThrows(RuntimeException.class, () -> {
manager.get(GROUP);
}, e -> {
Assert.assertContains("does not exists", e.getMessage());
});
Assert.assertThrows(RuntimeException.class, () -> {
manager.get(GROUP2);
}, e -> {
Assert.assertContains("does not exists", e.getMessage());
});
}
}

View File

@ -0,0 +1,93 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.unit.concurrent;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.hugegraph.util.ExecutorUtil;
import org.junit.Assert;
import org.junit.Test;
import org.apache.hugegraph.concurrent.PausableScheduledThreadPool;
public class PausableScheduledThreadPoolTest {
@Test
public void testScheduleWithFixedDelay() throws InterruptedException {
PausableScheduledThreadPool executor =
ExecutorUtil.newPausableScheduledThreadPool("test");
long period = 500L;
AtomicInteger counter = new AtomicInteger(0);
executor.scheduleWithFixedDelay(() -> {
System.out.println("counter: " + counter.incrementAndGet());
}, period, period, TimeUnit.MILLISECONDS);
Thread.sleep((long) (2.1 * period));
Assert.assertEquals(2, counter.get());
// pause
executor.pauseSchedule();
Thread.sleep(period);
Assert.assertEquals(2, counter.get());
// resume
executor.resumeSchedule();
Thread.sleep((long) (0.5 * period));
Assert.assertEquals(3, counter.get());
Thread.sleep((long) (0.6 * period));
Assert.assertEquals(4, counter.get());
// pause again
executor.pauseSchedule();
executor.shutdown();
executor.awaitTermination(3L, TimeUnit.SECONDS);
}
@Test
public void testScheduleWithFixedRate() throws InterruptedException {
PausableScheduledThreadPool executor =
ExecutorUtil.newPausableScheduledThreadPool(2, "test");
long period = 500L;
AtomicInteger counter = new AtomicInteger(0);
executor.scheduleAtFixedRate(() -> {
System.out.println("counter: " + counter.incrementAndGet());
}, period, period, TimeUnit.MILLISECONDS);
Thread.sleep((long) (2.1 * period));
Assert.assertEquals(2, counter.get());
// pause
executor.pauseSchedule();
Thread.sleep(period);
Assert.assertEquals(2, counter.get());
// resume
executor.resumeSchedule();
Thread.sleep((long) (1.1 * period));
Assert.assertEquals(4, counter.get());
// pause again
executor.pauseSchedule();
executor.shutdownNow();
executor.awaitTermination(3L, TimeUnit.SECONDS);
}
}

View File

@ -0,0 +1,159 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.unit.concurrent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import org.junit.Test;
import org.apache.hugegraph.concurrent.RowLock;
import org.apache.hugegraph.testutil.Assert;
import org.apache.hugegraph.unit.BaseUnitTest;
import com.google.common.collect.ImmutableSet;
public class RowLockTest extends BaseUnitTest {
private static final int THREADS_NUM = 8;
@Test
public void testRowLock() {
RowLock<Integer> lock = new RowLock<>();
// Regular lock and unlock
lock.lock(1);
lock.unlock(1);
// Lock one lock multiple times
lock.lock(1);
lock.lock(1);
lock.unlock(1);
lock.unlock(1);
// Unlock one lock multiple times
lock.lock(1);
lock.unlock(1);
lock.unlock(1);
Assert.assertThrows(IllegalArgumentException.class, () -> {
lock.lock(null);
}, e -> {
Assert.assertContains("Lock key can't be null", e.getMessage());
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
lock.unlock(null);
}, e -> {
Assert.assertContains("Unlock key can't be null", e.getMessage());
});
}
@Test
public void testRowLockMultiRows() {
RowLock<Integer> lock = new RowLock<>();
lock.lockAll(ImmutableSet.of(1, 2, 3));
lock.unlockAll(ImmutableSet.of(1, 2, 3));
Assert.assertThrows(IllegalArgumentException.class, () -> {
lock.lockAll(null);
}, e -> {
Assert.assertContains("Lock keys can't be null", e.getMessage());
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
lock.unlockAll(null);
}, e -> {
Assert.assertContains("Unlock keys can't be null", e.getMessage());
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
lock.lockAll(ImmutableSet.of());
}, e -> {
Assert.assertContains("Lock keys can't be null or empty",
e.getMessage());
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
lock.unlockAll(ImmutableSet.of());
}, e -> {
Assert.assertContains("Unlock keys can't be null or empty",
e.getMessage());
});
}
@Test
public void testRowLockWithMultiThreads() {
RowLock<Integer> lock = new RowLock<>();
Set<String> names = new HashSet<>(THREADS_NUM);
List<Integer> keys = new ArrayList<>(5);
Random random = new Random();
for (int i = 0; i < 5; i++) {
keys.add(random.nextInt(THREADS_NUM));
}
Assert.assertEquals(0, names.size());
runWithThreads(THREADS_NUM, () -> {
lock.lockAll(new HashSet<>(keys));
names.add(Thread.currentThread().getName());
lock.unlockAll(new HashSet<>(keys));
});
Assert.assertEquals(THREADS_NUM, names.size());
}
@Test
public void testRowLockWithMultiThreadsLockOneKey() {
RowLock<Integer> lock = new RowLock<>();
Set<String> names = new HashSet<>(THREADS_NUM);
Assert.assertEquals(0, names.size());
Integer key = 1;
runWithThreads(THREADS_NUM, () -> {
lock.lock(key);
names.add(Thread.currentThread().getName());
lock.unlock(key);
});
Assert.assertEquals(THREADS_NUM, names.size());
}
@Test
public void testRowLockWithMultiThreadsWithRandomKey() {
RowLock<Integer> lock = new RowLock<>();
Set<String> names = new HashSet<>(THREADS_NUM);
Assert.assertEquals(0, names.size());
runWithThreads(THREADS_NUM, () -> {
List<Integer> keys = new ArrayList<>(5);
Random random = new Random();
for (int i = 0; i < 5; i++) {
keys.add(random.nextInt(THREADS_NUM));
}
lock.lockAll(new HashSet<>(keys));
names.add(Thread.currentThread().getName());
lock.unlockAll(new HashSet<>(keys));
});
Assert.assertEquals(THREADS_NUM, names.size());
}
}

View File

@ -0,0 +1,673 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.unit.config;
import static org.apache.hugegraph.config.OptionChecker.allowValues;
import static org.apache.hugegraph.config.OptionChecker.disallowEmpty;
import static org.apache.hugegraph.config.OptionChecker.inValues;
import static org.apache.hugegraph.config.OptionChecker.nonNegativeInt;
import static org.apache.hugegraph.config.OptionChecker.positiveInt;
import static org.apache.hugegraph.config.OptionChecker.rangeDouble;
import static org.apache.hugegraph.config.OptionChecker.rangeInt;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.hugegraph.config.ConfigConvOption;
import org.apache.hugegraph.config.ConfigException;
import org.apache.hugegraph.config.ConfigListConvOption;
import org.apache.hugegraph.config.ConfigListOption;
import org.apache.hugegraph.config.ConfigOption;
import org.apache.hugegraph.config.HugeConfig;
import org.apache.hugegraph.config.OptionHolder;
import org.apache.hugegraph.config.OptionSpace;
import org.apache.hugegraph.testutil.Assert;
import org.apache.hugegraph.unit.BaseUnitTest;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.configuration2.AbstractConfiguration;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.MapConfiguration;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.io.FileHandler;
import org.apache.commons.io.FileUtils;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
public class HugeConfigTest extends BaseUnitTest {
private static final String PATH =
"src/test/java/org/apache/hugegraph/unit/config/";
private static final String CONF = PATH + "test.conf";
@BeforeClass
public static void init() {
OptionSpace.register("test", TestOptions.class.getName());
}
@Test
public void testOptionDataType() {
Assert.assertEquals(String.class, TestOptions.text1.dataType());
Assert.assertEquals(Integer.class, TestOptions.int1.dataType());
Assert.assertEquals(Long.class, TestOptions.long1.dataType());
Assert.assertEquals(Float.class, TestOptions.float1.dataType());
Assert.assertEquals(Double.class, TestOptions.double1.dataType());
Assert.assertEquals(Boolean.class, TestOptions.bool.dataType());
Assert.assertEquals(Class.class, TestOptions.clazz.dataType());
Assert.assertEquals(List.class, TestOptions.list.dataType());
Assert.assertEquals(List.class, TestOptions.map.dataType());
Assert.assertEquals(String.class, TestOptions.weekday.dataType());
Assert.assertEquals(List.class, TestOptions.weekdays.dataType());
}
@Test
public void testOptionDesc() {
Assert.assertEquals("description of group1.text1",
TestOptions.text1.desc());
Assert.assertEquals("description of group1.text2 sub",
TestSubOptions.text2.desc());
}
@Test
public void testOptionRequired() {
Assert.assertFalse(TestOptions.text1.required());
Assert.assertTrue(TestSubOptions.text2.required());
}
@Test
public void testOptionsToString() {
Assert.assertEquals("[String]group1.text1=text1-value",
TestOptions.text1.toString());
Assert.assertEquals("[Integer]group1.int1=1",
TestOptions.int1.toString());
Assert.assertEquals("[Long]group1.long1=100",
TestOptions.long1.toString());
Assert.assertEquals("[Float]group1.float1=100.0",
TestOptions.float1.toString());
Assert.assertEquals("[Double]group1.double1=100.0",
TestOptions.double1.toString());
Assert.assertEquals("[Boolean]group1.bool=true",
TestOptions.bool.toString());
Assert.assertEquals("[Class]group1.class=class java.lang.Object",
TestOptions.clazz.toString());
Assert.assertEquals("[List]group1.list=[list-value1, list-value2]",
TestOptions.list.toString());
Assert.assertEquals("[List]group1.map=[key1:value1, key2:value2]",
TestOptions.map.toString());
Assert.assertEquals("[String]group1.text1=text1-value",
TestSubOptions.text1.toString());
Assert.assertEquals("[String]group1.text2=text2-value-override",
TestSubOptions.text2.toString());
Assert.assertEquals("[String]group1.textsub=textsub-value",
TestSubOptions.textsub.toString());
}
@Test
public void testOptionWithError() {
Assert.assertThrows(ConfigException.class, () -> {
new ConfigOption<>(
"group1.text",
"description of group1.text",
disallowEmpty(),
""
);
});
Assert.assertThrows(ConfigException.class, () -> {
new ConfigOption<>(
"group1.choice",
"description of group1.choice",
allowValues("CHOICE-1", "CHOICE-2", "CHOICE-3"),
"CHOICE-4"
);
});
Assert.assertThrows(ConfigException.class, () -> {
new ConfigListOption<>(
"group1.list",
true,
"description of group1.list",
disallowEmpty(),
String.class,
ImmutableList.of()
);
});
Assert.assertThrows(ConfigException.class, () -> {
new ConfigOption<>(
"group1.int",
"description of group1.int",
positiveInt(),
0
);
});
Assert.assertThrows(ConfigException.class, () -> {
new ConfigOption<>(
"group1.int",
"description of group1.int",
nonNegativeInt(),
-1
);
});
Assert.assertThrows(ConfigException.class, () -> {
new ConfigOption<>(
"group1.long",
"description of group1.long",
rangeInt(1L, 100L),
0L
);
});
Assert.assertThrows(ConfigException.class, () -> {
new ConfigOption<>(
"group1.long",
"description of group1.long",
rangeInt(1L, 100L),
101L
);
});
Assert.assertThrows(ConfigException.class, () -> {
new ConfigOption<>(
"group1.double",
"description of group1.double",
rangeDouble(1D, 10D),
0D
);
});
Assert.assertThrows(ConfigException.class, () -> {
new ConfigOption<>(
"group1.double",
"description of group1.double",
rangeDouble(1D, 10D),
11D
);
});
Assert.assertThrows(ConfigException.class, () -> {
new ConfigOption<>(
"group1.class",
"description of group1.class",
input -> input != null && input.equals(Long.class),
Integer.class
);
});
Assert.assertThrows(ConfigException.class, () -> {
new ConfigListOption<>(
"group1.list",
"description of list with invalid default values",
disallowEmpty()
);
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
new ConfigListOption<>(
"group1.list",
"description of list with invalid default values",
null
);
});
Assert.assertThrows(ConfigException.class, () -> {
new ConfigListConvOption<String, WeekDay>(
"group1.list_conv",
"description of list_conv with invalid default values",
disallowEmpty(),
s -> WeekDay.valueOf(s)
);
});
Assert.assertThrows(IllegalArgumentException.class, () -> {
new ConfigListConvOption<String, WeekDay>(
"group1.list_conv",
"description of list_conv with invalid default values",
null,
s -> WeekDay.valueOf(s)
);
});
}
@Test
public void testHugeConfig() throws Exception {
Configuration conf = new PropertiesConfiguration();
Assert.assertEquals(DisabledListDelimiterHandler.INSTANCE,
((AbstractConfiguration) conf).getListDelimiterHandler());
HugeConfig config = new HugeConfig(conf);
Assert.assertEquals("text1-value", config.get(TestOptions.text1));
Assert.assertEquals("text2-value", config.get(TestOptions.text2));
Assert.assertEquals("CHOICE-1", config.get(TestOptions.text3));
Assert.assertEquals(1, (int) config.get(TestOptions.int1));
Assert.assertEquals(10, (int) config.get(TestOptions.int2));
Assert.assertEquals(10, (int) config.get(TestOptions.int3));
Assert.assertEquals(100L, (long) config.get(TestOptions.long1));
Assert.assertEquals(100.0f, config.get(TestOptions.float1), 0f);
Assert.assertEquals(100.0f, config.get(TestOptions.double1), 0d);
Assert.assertEquals(true, config.get(TestOptions.bool));
Assert.assertEquals(Object.class, config.get(TestOptions.clazz));
Assert.assertThrows(ConfigException.class, () -> {
config.setProperty(TestOptions.clazz.name(),
"org.apache.hugegraph.HugeGraph");
}, e -> {
Assert.assertTrue(e.getCause() instanceof ClassNotFoundException);
});
Assert.assertEquals(Arrays.asList("list-value1", "list-value2"),
config.get(TestOptions.list));
Assert.assertEquals(ImmutableMap.of("key1", "value1", "key2", "value2"),
config.getMap(TestOptions.map));
Assert.assertEquals(WeekDay.WEDNESDAY, config.get(TestOptions.weekday));
Assert.assertEquals(Arrays.asList(WeekDay.SATURDAY, WeekDay.SUNDAY),
config.get(TestOptions.weekdays));
Assert.assertThrows(ConfigException.class, () -> {
new HugeConfig((Configuration) null);
});
}
@Test
public void testHugeConfigWithFile() throws Exception {
HugeConfig config = new HugeConfig(CONF);
Assert.assertEquals("file-text1-value", config.get(TestOptions.text1));
Assert.assertEquals("file-text2-value", config.get(TestOptions.text2));
Assert.assertEquals("CHOICE-3", config.get(TestOptions.text3));
Assert.assertEquals(2, (int) config.get(TestOptions.int1));
Assert.assertEquals(0, (int) config.get(TestOptions.int2));
Assert.assertEquals(1, (int) config.get(TestOptions.int3));
Assert.assertEquals(99L, (long) config.get(TestOptions.long1));
Assert.assertEquals(66.0f, config.get(TestOptions.float1), 0f);
Assert.assertEquals(66.0f, config.get(TestOptions.double1), 0d);
Assert.assertEquals(false, config.get(TestOptions.bool));
Assert.assertEquals(String.class, config.get(TestOptions.clazz));
Assert.assertEquals(Arrays.asList("file-v1", "file-v2", "file-v3"),
config.get(TestOptions.list));
Assert.assertEquals(ImmutableMap.of("key1", "value1", "key3", "value3"),
config.getMap(TestOptions.map));
Assert.assertEquals(WeekDay.SUNDAY, config.get(TestOptions.weekday));
Assert.assertEquals(Arrays.asList(WeekDay.SATURDAY, WeekDay.FRIDAY),
config.get(TestOptions.weekdays));
}
@Test
public void testHugeConfigWithConfiguration() throws Exception {
PropertiesConfiguration configuration = new PropertiesConfiguration();
FileHandler fileHandler = new FileHandler(configuration);
fileHandler.load(CONF);
HugeConfig config = new HugeConfig(configuration);
Assert.assertEquals("file-text1-value", config.get(TestOptions.text1));
Assert.assertEquals("file-text2-value", config.get(TestOptions.text2));
Assert.assertEquals("CHOICE-3", config.get(TestOptions.text3));
}
@Test
public void testHugeConfigWithOverride() throws Exception {
Configuration conf = new PropertiesConfiguration();
Assert.assertEquals(DisabledListDelimiterHandler.INSTANCE,
((AbstractConfiguration) conf).getListDelimiterHandler());
HugeConfig config = new HugeConfig(conf);
Assert.assertEquals("text1-value", config.get(TestSubOptions.text1));
Assert.assertEquals("text2-value-override",
config.get(TestSubOptions.text2));
Assert.assertEquals("textsub-value",
config.get(TestSubOptions.textsub));
}
@Test
public void testHugeConfigWithTypeError() {
OptionSpace.register("test-type-error",
TestOptionsWithTypeError.class.getName());
Assert.assertThrows(ConfigException.class, () -> {
new HugeConfig(PATH + "test-type-error.conf");
});
}
@Test
public void testHugeConfigWithCheckError() throws Exception {
OptionSpace.register("test-check-error",
TestOptionsWithCheckError.class.getName());
Assert.assertThrows(ConfigException.class, () -> {
new HugeConfig(PATH + "test-check-error.conf");
});
}
@Test
public void testHugeConfigWithListOptionError() throws Exception {
OptionSpace.register("test-list-error",
TestOptionsWithListError.class.getName());
Assert.assertThrows(IllegalStateException.class, () -> {
new HugeConfig(PATH + "test-list-error.conf");
});
}
@Test
public void testSaveHugeConfig() throws ConfigurationException,
IOException {
HugeConfig config = new HugeConfig(CONF);
Assert.assertEquals("file-text1-value", config.get(TestOptions.text1));
File copiedFile = new File("copied.conf");
config.save(copiedFile);
Assert.assertTrue(copiedFile.exists());
Assert.assertTrue(copiedFile.length() > 0);
try {
HugeConfig copiedConfig = new HugeConfig(copiedFile.getPath());
Assert.assertEquals(IteratorUtils.toList(config.getKeys()),
IteratorUtils.toList(copiedConfig.getKeys()));
Assert.assertEquals(config.get(TestOptions.text1),
copiedConfig.get(TestOptions.text1));
} finally {
FileUtils.forceDelete(copiedFile);
}
}
@Test
public void testFromMapConfigurationWithList() {
Map<String, String> options = new HashMap<>();
options.put(TestOptions.list.name(), "[a, b]");
MapConfiguration mapConfiguration = new MapConfiguration(options);
HugeConfig hugeConfig = new HugeConfig(mapConfiguration);
List<String> values = hugeConfig.get(TestOptions.list);
Assert.assertEquals(2, values.size());
Assert.assertTrue(values.contains("a"));
Assert.assertTrue(values.contains("b"));
}
public static class TestOptions extends OptionHolder {
private static volatile TestOptions instance;
public static synchronized TestOptions instance() {
if (instance == null) {
instance = new TestOptions();
instance.registerOptions();
}
return instance;
}
public static final ConfigOption<String> text1 =
new ConfigOption<>(
"group1.text1",
"description of group1.text1",
disallowEmpty(),
"text1-value"
);
public static final ConfigOption<String> text2 =
new ConfigOption<>(
"group1.text2",
"description of group1.text2",
"text2-value"
);
public static final ConfigOption<String> text3 =
new ConfigOption<>(
"group1.text3",
"description of group1.text3",
allowValues("CHOICE-1", "CHOICE-2", "CHOICE-3"),
"CHOICE-1"
);
public static final ConfigOption<Integer> int1 =
new ConfigOption<>(
"group1.int1",
"description of group1.int1",
rangeInt(1, 100),
1
);
public static final ConfigOption<Integer> int2 =
new ConfigOption<>(
"group1.int2",
"description of group1.int2",
nonNegativeInt(),
10
);
public static final ConfigOption<Integer> int3 =
new ConfigOption<>(
"group1.int3",
"description of group1.int3",
positiveInt(),
10
);
public static final ConfigOption<Long> long1 =
new ConfigOption<>(
"group1.long1",
"description of group1.long1",
rangeInt(1L, 100L),
100L
);
public static final ConfigOption<Float> float1 =
new ConfigOption<>(
"group1.float1",
"description of group1.float1",
rangeDouble(1.0f, 100.0f),
100.0f
);
public static final ConfigOption<Double> double1 =
new ConfigOption<>(
"group1.double1",
"description of group1.double1",
rangeDouble(1.0, 100.0),
100.0
);
public static final ConfigOption<Boolean> bool =
new ConfigOption<>(
"group1.bool",
"description of group1.bool",
disallowEmpty(),
true
);
public static final ConfigOption<Class<?>> clazz =
new ConfigOption<>(
"group1.class",
"description of group1.class",
disallowEmpty(),
Object.class
);
public static final ConfigConvOption<String, WeekDay> weekday =
new ConfigConvOption<>(
"group1.weekday",
"description of group1.weekday",
allowValues("SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY",
"THURSDAY", "FRIDAY", "SATURDAY"),
WeekDay::valueOf,
"WEDNESDAY"
);
public static final ConfigListConvOption<String, WeekDay> weekdays =
new ConfigListConvOption<>(
"group1.weekdays",
"description of group1.weekdays",
inValues("SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY",
"THURSDAY", "FRIDAY", "SATURDAY"),
WeekDay::valueOf,
"SATURDAY", "SUNDAY"
);
public static final ConfigListOption<String> list =
new ConfigListOption<>(
"group1.list",
"description of group1.list",
disallowEmpty(),
"list-value1", "list-value2"
);
public static final ConfigListOption<String> map =
new ConfigListOption<>(
"group1.map",
"description of group1.map",
disallowEmpty(),
"key1:value1", "key2:value2"
);
}
public static class TestSubOptions extends TestOptions {
public static final ConfigOption<String> text2 =
new ConfigOption<>(
"group1.text2",
true,
"description of group1.text2 sub",
disallowEmpty(),
String.class,
"text2-value-override"
);
public static final ConfigOption<String> textsub =
new ConfigOption<>(
"group1.textsub",
"description of group1.textsub",
disallowEmpty(),
"textsub-value"
);
}
public static class TestOptionsWithTypeError extends OptionHolder {
private static volatile TestOptionsWithTypeError instance;
public static synchronized TestOptionsWithTypeError instance() {
if (instance == null) {
instance = new TestOptionsWithTypeError();
instance.registerOptions();
}
return instance;
}
public static final ConfigOption<Integer> intError =
new ConfigOption<>(
"group1.int_type_error",
"description of group1.int_type_error",
rangeInt(1, 100),
1
);
}
public static class TestOptionsWithCheckError extends OptionHolder {
private static volatile TestOptionsWithCheckError instance;
public static synchronized TestOptionsWithCheckError instance() {
if (instance == null) {
instance = new TestOptionsWithCheckError();
instance.registerOptions();
}
return instance;
}
public static final ConfigOption<Integer> intError =
new ConfigOption<>(
"group1.int_check_error",
"description of group1.int_check_error",
rangeInt(1, 100),
1
);
}
public static class TestOptionsWithListError extends OptionHolder {
private static volatile TestOptionsWithListError instance;
public static synchronized TestOptionsWithListError instance() {
if (instance == null) {
instance = new TestOptionsWithListError();
instance.registerOptions();
}
return instance;
}
public static final InvalidConfigListOption<Integer> listError =
new InvalidConfigListOption<>(
"group1.list_for_list_error",
"description of group1.list_for_list_error",
disallowEmpty(),
1
);
static class InvalidConfigListOption<T> extends ConfigOption<List<T>> {
@SuppressWarnings("unchecked")
public InvalidConfigListOption(String name, String desc,
Predicate<List<T>> pred,
T... values) {
super(name, false, desc, pred,
(Class<List<T>>) Arrays.asList(values).getClass(),
Arrays.asList(values));
}
@Override
protected boolean forList() {
return false;
}
}
}
public enum WeekDay {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
}

View File

@ -0,0 +1,215 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.apache.hugegraph.unit.config;
import static org.apache.hugegraph.config.OptionChecker.disallowEmpty;
import org.junit.Test;
import org.apache.hugegraph.config.OptionSpace;
import org.apache.hugegraph.config.OptionHolder;
import org.apache.hugegraph.config.ConfigException;
import org.apache.hugegraph.config.ConfigOption;
import org.apache.hugegraph.testutil.Assert;
import org.apache.hugegraph.unit.BaseUnitTest;
import com.google.common.base.Predicate;
public class OptionSpaceTest extends BaseUnitTest {
@Test
public void tesRegister() {
int oldSize = OptionSpace.keys().size();
OptionSpace.register("testgroup1", OptionHolder1.class.getName());
Assert.assertEquals(oldSize + 2, OptionSpace.keys().size());
Assert.assertTrue(OptionSpace.containKey("testgroup1.text1"));
Assert.assertTrue(OptionSpace.containKey("testgroup1.text2"));
OptionSpace.register("testgroup1", new OptionHolder1());
Assert.assertEquals(oldSize + 2, OptionSpace.keys().size());
Assert.assertTrue(OptionSpace.containKey("testgroup1.text1"));
Assert.assertTrue(OptionSpace.containKey("testgroup1.text2"));
OptionSpace.register("testgroup2", OptionHolder2.class.getName());
Assert.assertEquals(oldSize + 4, OptionSpace.keys().size());
Assert.assertTrue(OptionSpace.containKey("testgroup1.text1"));
Assert.assertTrue(OptionSpace.containKey("testgroup1.text2"));
Assert.assertTrue(OptionSpace.containKey("testgroup2.text1"));
Assert.assertTrue(OptionSpace.containKey("testgroup2.text2"));
Assert.assertEquals("text1 value",
OptionSpace.get("testgroup1.text1").defaultValue());
Assert.assertEquals("text2 value",
OptionSpace.get("testgroup1.text2").defaultValue());
Assert.assertEquals("text1 value",
OptionSpace.get("testgroup2.text1").defaultValue());
Assert.assertEquals("text2 value",
OptionSpace.get("testgroup2.text2").defaultValue());
}
@Test
public void testRegisterWithError() {
Assert.assertThrows(ConfigException.class, () -> {
OptionSpace.register("test-error", "fake");
});
Assert.assertThrows(ConfigException.class, () -> {
OptionSpace.register("test-error", Exception.class.getName());
});
Assert.assertThrows(ConfigException.class, () -> {
OptionSpace.register("test-error",
OptionHolderWithoutInstance.class.getName());
});
Assert.assertThrows(ConfigException.class, () -> {
OptionSpace.register("test-error", OptionHolderWithNonStaticInstance
.class.getName());
});
Assert.assertThrows(ConfigException.class, () -> {
OptionSpace.register("test-error",
OptionHolderWithInstanceNull.class.getName());
});
Assert.assertThrows(ConfigException.class, () -> {
OptionSpace.register("test-error",
OptionHolderWithInstanceThrow.class.getName());
});
Assert.assertThrows(ConfigException.class, () -> {
OptionSpace.register("test-error",
OptionHolderWithInvalidOption.class.getName());
});
}
public static class OptionHolderWithoutInstance extends OptionHolder {
// no instance()
}
public static class OptionHolderWithNonStaticInstance extends OptionHolder {
// not static instance()
public OptionHolderWithNonStaticInstance instance() {
return new OptionHolderWithNonStaticInstance();
}
}
public static class OptionHolderWithInstanceNull extends OptionHolder {
public static OptionHolderWithInstanceNull instance() {
return null;
}
}
public static class OptionHolderWithInstanceThrow extends OptionHolder {
public static OptionHolderWithInstanceNull instance() {
throw new RuntimeException("test error");
}
}
public static class OptionHolderWithInvalidOption extends OptionHolder {
public static OptionHolderWithInvalidOption instance() {
return new OptionHolderWithInvalidOption();
}
private OptionHolderWithInvalidOption() {
this.registerOptions();
}
public static final String fake = "fake";
public static final ConfigOption<String> invalid =
new InvalidOption<>(
"group1.text1",
"description of group1.text1",
disallowEmpty(),
"value"
);
public static class InvalidOption<T> extends ConfigOption<T> {
public InvalidOption(String name, String desc,
Predicate<T> pred, T value) {
super(name, desc, pred, value);
}
@Override
public String name() {
throw new RuntimeException("fake");
}
}
}
public static class OptionHolder1 extends OptionHolder {
public static OptionHolder1 instance() {
return new OptionHolder1();
}
OptionHolder1() {
this.registerOptions();
}
public static final ConfigOption<String> text1 =
new ConfigOption<>(
"testgroup1.text1",
"description of testgroup1.text1",
disallowEmpty(),
"text1 value"
);
public static final ConfigOption<String> text2 =
new ConfigOption<>(
"testgroup1.text2",
"description of testgroup1.text2",
disallowEmpty(),
"text2 value"
);
}
public static class OptionHolder2 extends OptionHolder {
public static OptionHolder2 instance() {
return new OptionHolder2();
}
OptionHolder2() {
this.registerOptions();
}
public static final ConfigOption<String> text1 =
new ConfigOption<>(
"testgroup2.text1",
"description of testgroup2.text1",
disallowEmpty(),
"text1 value"
);
public static final ConfigOption<String> text2 =
new ConfigOption<>(
"testgroup2.text2",
"description of testgroup2.text2",
disallowEmpty(),
"text2 value"
);
}
}

Some files were not shown because too many files have changed in this diff Show More