• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

uber / h3-java / #641

22 Jun 2026 02:50PM UTC coverage: 95.602% (-1.4%) from 96.954%
#641

Pull #209

github

web-flow
Merge 446dbd57c into 4e16a5179
Pull Request #209: Fix #97: Allow custom native library directory via h3.native.dir

176 of 195 branches covered (90.26%)

Branch coverage included in aggregate %.

5 of 10 new or added lines in 1 file covered. (50.0%)

563 of 578 relevant lines covered (97.4%)

0.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.16
/src/main/java/com/uber/h3core/H3CoreLoader.java
1
/*
2
 * Copyright 2017-2018, 2024 Uber Technologies, Inc.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *         http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.uber.h3core;
17

18
import java.io.File;
19
import java.io.FileOutputStream;
20
import java.io.IOException;
21
import java.io.InputStream;
22
import java.io.OutputStream;
23
import java.nio.file.Files;
24
import java.nio.file.attribute.FileAttribute;
25
import java.nio.file.attribute.PosixFilePermission;
26
import java.nio.file.attribute.PosixFilePermissions;
27
import java.util.Locale;
28
import java.util.Set;
29

30
/** Extracts the native H3 core library to the local filesystem and loads it. */
31
public final class H3CoreLoader {
32
  private H3CoreLoader() {
33
    // Prevent instantiation
34
  }
35

36
  /**
37
   * Locale used when handling system strings that need to be mapped to resource names. English is
38
   * used since that locale was used when building the library.
39
   */
40
  static final Locale H3_CORE_LOCALE = Locale.ENGLISH;
1✔
41

42
  // Supported H3 architectures
43
  static final String ARCH_X64 = "x64";
44
  static final String ARCH_X86 = "x86";
45
  static final String ARCH_ARM64 = "arm64";
46

47
  private static volatile File libraryFile = null;
1✔
48

49
  /** Read all bytes from <code>in</code> and write them to <code>out</code>. */
50
  private static void copyStream(InputStream in, OutputStream out) throws IOException {
51
    byte[] buf = new byte[4096];
1✔
52

53
    int read;
54
    while ((read = in.read(buf)) != -1) {
1✔
55
      out.write(buf, 0, read);
1✔
56
    }
57
  }
1✔
58

59
  /**
60
   * Copy the resource at the given path to the file. File will be made readable, writable, and
61
   * executable.
62
   *
63
   * @param resourcePath Resource to copy
64
   * @param newH3LibFile File to write
65
   * @throws UnsatisfiedLinkError The resource path does not exist
66
   */
67
  static void copyResource(String resourcePath, File newH3LibFile) throws IOException {
68
    // Shove the resource into the file and close it
69
    try (InputStream resource = H3CoreLoader.class.getResourceAsStream(resourcePath)) {
1✔
70
      if (resource == null) {
1✔
71
        throw new UnsatisfiedLinkError(
1✔
72
            String.format("No native resource found at %s", resourcePath));
1✔
73
      }
74

75
      try (FileOutputStream outFile = new FileOutputStream(newH3LibFile)) {
1✔
76
        copyStream(resource, outFile);
1✔
77
      }
78
    }
79
  }
1✔
80

81
  /**
82
   * For use when the H3 library should be unpacked from the JAR and loaded.
83
   *
84
   * @throws SecurityException Loading the library was not allowed by the SecurityManager.
85
   * @throws UnsatisfiedLinkError The library could not be loaded
86
   * @throws IOException Failed to unpack the library
87
   */
88
  public static NativeMethods loadNatives() throws IOException {
89
    final OperatingSystem os =
1✔
90
        detectOs(System.getProperty("java.vendor"), System.getProperty("os.name"));
1✔
91
    final String arch = detectArch(System.getProperty("os.arch"));
1✔
92

93
    return loadNatives(os, arch);
1✔
94
  }
95

96
  private static File createTempLibraryFile(OperatingSystem os) throws IOException {
97
    // Check if the user specified a custom directory for native libraries
98
    String customDir = System.getProperty("h3.native.dir");
1✔
99
    File dir = customDir != null ? new File(customDir) : null;
1!
100
    
101
    // Ensure the custom directory exists
102
    if (dir != null && !dir.exists()) {
1!
NEW
103
      dir.mkdirs();
×
104
    }
105

106
    if (os.isPosix()) {
1!
107
      // Note this is already done by the implementation of Files.createTempFile that I looked at,
108
      // but the javadoc does not seem to gaurantee the permissions will be restricted to owner
109
      // write.
110
      final FileAttribute<Set<PosixFilePermission>> attr =
1✔
111
          PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"));
1✔
112
      
113
      if (dir != null) {
1!
NEW
114
        return Files.createTempFile(dir.toPath(), "libh3-java", os.getSuffix(), attr).toFile();
×
115
      } else {
116
        return Files.createTempFile("libh3-java", os.getSuffix(), attr).toFile();
1✔
117
      }
118
    } else {
119
      // When not a POSIX OS, try to ensure the permissions are secure
120
      final File f;
NEW
121
      if (dir != null) {
×
NEW
122
        f = Files.createTempFile(dir.toPath(), "libh3-java", os.getSuffix()).toFile();
×
123
      } else {
NEW
124
        f = Files.createTempFile("libh3-java", os.getSuffix()).toFile();
×
125
      }
126
      f.setReadable(true, true);
×
127
      f.setWritable(true, true);
×
128
      f.setExecutable(true, true);
×
129
      return f;
×
130
    }
131
  }
132

133
  /**
134
   * For use when the H3 library should be unpacked from the JAR and loaded. The native library for
135
   * the specified operating system and architecture will be extract.
136
   *
137
   * <p>H3 will only successfully extract the library once, even if different operating system and
138
   * architecture are specified, or if {@link #loadNatives()} was used instead.
139
   *
140
   * @throws SecurityException Loading the library was not allowed by the SecurityManager.
141
   * @throws UnsatisfiedLinkError The library could not be loaded
142
   * @throws IOException Failed to unpack the library
143
   * @param os Operating system whose lobrary should be used
144
   * @param arch Architecture name, as packaged in the H3 library
145
   */
146
  public static synchronized NativeMethods loadNatives(OperatingSystem os, String arch)
147
      throws IOException {
148
    // This is synchronized because if multiple threads were writing and
149
    // loading the shared object at the same time, bad things could happen.
150

151
    if (libraryFile == null) {
1✔
152
      final String dirName = String.format("%s-%s", os.getDirName(), arch);
1✔
153
      final String libName = String.format("libh3-java%s", os.getSuffix());
1✔
154

155
      final File newLibraryFile = createTempLibraryFile(os);
1✔
156
      newLibraryFile.deleteOnExit();
1✔
157

158
      copyResource(String.format("/%s/%s", dirName, libName), newLibraryFile);
1✔
159

160
      System.load(newLibraryFile.getCanonicalPath());
1✔
161

162
      libraryFile = newLibraryFile;
1✔
163
    }
164

165
    return new NativeMethods();
1✔
166
  }
167

168
  /**
169
   * For use when the H3 library is installed system-wide and Java is able to locate it.
170
   *
171
   * @throws SecurityException Loading the library was not allowed by the SecurityManager.
172
   * @throws UnsatisfiedLinkError The library could not be loaded
173
   */
174
  public static NativeMethods loadSystemNatives() {
175
    System.loadLibrary("h3-java");
×
176

177
    return new NativeMethods();
×
178
  }
179

180
  /** Operating systems supported by H3-Java. */
181
  public enum OperatingSystem {
1✔
182
    ANDROID(".so"),
1✔
183
    DARWIN(".dylib"),
1✔
184
    FREEBSD(".so"),
1✔
185
    WINDOWS(".dll", false),
1✔
186
    LINUX(".so");
1✔
187

188
    private final String suffix;
189
    private final boolean posix;
190

191
    OperatingSystem(String suffix) {
1✔
192
      this.suffix = suffix;
1✔
193
      this.posix = true;
1✔
194
    }
1✔
195

196
    OperatingSystem(String suffix, boolean posix) {
1✔
197
      this.suffix = suffix;
1✔
198
      this.posix = posix;
1✔
199
    }
1✔
200

201
    /** Suffix for native libraries. */
202
    public String getSuffix() {
203
      return suffix;
1✔
204
    }
205

206
    /** How this operating system's name is rendered when extracting the native library. */
207
    public String getDirName() {
208
      return name().toLowerCase(H3_CORE_LOCALE);
1✔
209
    }
210

211
    /** Whether to try to use POSIX file permissions when creating the native library temp file. */
212
    public boolean isPosix() {
213
      return this.posix;
1✔
214
    }
215
  }
216

217
  /**
218
   * Detect the current operating system.
219
   *
220
   * @param javaVendor Value of system property "java.vendor"
221
   * @param osName Value of system property "os.name"
222
   */
223
  static final OperatingSystem detectOs(String javaVendor, String osName) {
224
    // Detecting Android using the properties from:
225
    // https://developer.android.com/reference/java/lang/System.html
226
    if (javaVendor.toLowerCase(H3_CORE_LOCALE).contains("android")) {
1✔
227
      return OperatingSystem.ANDROID;
1✔
228
    }
229

230
    String javaOs = osName.toLowerCase(H3_CORE_LOCALE);
1✔
231
    if (javaOs.contains("mac")) {
1✔
232
      return OperatingSystem.DARWIN;
1✔
233
    } else if (javaOs.contains("win")) {
1✔
234
      return OperatingSystem.WINDOWS;
1✔
235
    } else if (javaOs.contains("freebsd")) {
1✔
236
      return OperatingSystem.FREEBSD;
1✔
237
    } else {
238
      // Only other supported platform
239
      return OperatingSystem.LINUX;
1✔
240
    }
241
  }
242

243
  /**
244
   * Detect the system architecture.
245
   *
246
   * @param osArch Value of system property "os.arch"
247
   */
248
  static final String detectArch(String osArch) {
249
    if (osArch.equals("amd64") || osArch.equals("x86_64")) {
1✔
250
      return ARCH_X64;
1✔
251
    } else if (osArch.equals("i386")
1✔
252
        || osArch.equals("i486")
1✔
253
        || osArch.equals("i586")
1✔
254
        || osArch.equals("i686")
1✔
255
        || osArch.equals("i786")
1✔
256
        || osArch.equals("i886")) {
1✔
257
      return ARCH_X86;
1✔
258
    } else if (osArch.equals("aarch64")) {
1✔
259
      return ARCH_ARM64;
1✔
260
    } else {
261
      return osArch;
1✔
262
    }
263
  }
264
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc