mirror of https://github.com/i2p-zero/i2p-zero
First attempt at reproducible builds. Seems to work on Linux/Mac, but Windows distributables are not yet producing consistent hashes due to jlink not currently being fully deterministic. Hopefully this will change in future JDK releases.
parent
ba43496762
commit
b2b03aade8
@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ $(uname -s) = Darwin ]; then
|
||||
basedir=$(dirname $(cd "$(dirname "$0")"; pwd -P))
|
||||
else
|
||||
basedir=$(dirname $(dirname $(readlink -fm $0)))
|
||||
fi
|
||||
|
||||
"$basedir"/bin/build-all.sh
|
||||
"$basedir"/bin/zip-all.sh
|
@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ $(uname -s) = Darwin ]; then
|
||||
basedir=$(dirname $(cd "$(dirname "$0")"; pwd -P))
|
||||
else
|
||||
basedir=$(dirname $(dirname $(readlink -fm $0)))
|
||||
fi
|
||||
|
||||
source "$basedir/bin/java-config.sh"
|
||||
source "$basedir/bin/util.sh"
|
||||
|
||||
echo "*** Compiling Zip normalizer utility"
|
||||
"$JAVA_HOME"/bin/javac --module-path import/commons-compress-1.18/commons-compress-1.18.jar -d target/classes/org.getmonero.util.normalizeZip $(find org.getmonero.util.normalizeZip/src -name '*.java')
|
||||
|
||||
echo "*** Packaging Zip normalizer as a modular jar"
|
||||
"$JAVA_HOME"/bin/jar --create --file target/org.getmonero.util.normalizeZip.jar --main-class org.getmonero.util.normalizeZip.NormalizeZip -C target/classes/org.getmonero.util.normalizeZip .
|
||||
|
||||
|
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ $(uname -s) = Darwin ]; then
|
||||
basedir=$(dirname $(cd "$(dirname "$0")"; pwd -P))
|
||||
else
|
||||
basedir=$(dirname $(dirname $(readlink -fm $0)))
|
||||
fi
|
||||
|
||||
rm -fr "$basedir/dist-zip" "$basedir/dist-zip-staging" "$basedir/dist" "$basedir/target" "$basedir/import" "$basedir/out"
|
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
# get the SHA-256 hash of the specified file
|
||||
getHash () {
|
||||
if [ $(uname -s) = Darwin ]; then
|
||||
h=`shasum -a 256 $1 | awk '{print $1}'`
|
||||
else
|
||||
h=`sha256sum $1 | awk '{print $1}'`
|
||||
fi
|
||||
echo $h
|
||||
}
|
||||
|
||||
# normalizes the specified jar or zip for reproducible build. Enforces consistent zip file order and sets all timestamps to the last modified date of the VERSION file
|
||||
normalizeZip () {
|
||||
java --module-path "$basedir/import/commons-compress-1.18/commons-compress-1.18.jar":"$basedir/target/org.getmonero.util.normalizeZip.jar" \
|
||||
-m org.getmonero.util.normalizeZip "$basedir/org.getmonero.i2p.zero/src/org/getmonero/i2p/zero/VERSION" "$1"
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../import/commons-compress-1.18/commons-compress-1.18.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,3 @@
|
||||
module org.getmonero.util.normalizeZip {
|
||||
requires org.apache.commons.compress;
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package org.getmonero.util.normalizeZip;
|
||||
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A stripper that runs several strippers one after the others,
|
||||
* where the input of one stripper is the output of the previous one.
|
||||
* This class implements the Design Pattern "Decorator".
|
||||
*/
|
||||
final class CompoundStripper implements Stripper {
|
||||
private final Stripper[] strippers;
|
||||
|
||||
/**
|
||||
* Constructs a compound stripper from a list of strippers.
|
||||
*
|
||||
* @param strippers the list of strippers.
|
||||
*/
|
||||
public CompoundStripper(Stripper... strippers) {
|
||||
this.strippers = strippers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void strip(File in, File out) throws IOException {
|
||||
final List<File> tmpFiles = new ArrayList<>();
|
||||
File currentIn = in;
|
||||
try {
|
||||
for (Stripper stripper : strippers) {
|
||||
final File tmp = Files.createTempFile(null, null).toFile();
|
||||
tmp.deleteOnExit();
|
||||
tmpFiles.add(tmp);
|
||||
stripper.strip(currentIn, tmp);
|
||||
currentIn = tmp;
|
||||
}
|
||||
Files.copy(currentIn.toPath(), out.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
} finally {
|
||||
for (File file : tmpFiles) {
|
||||
Files.delete(file.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package org.getmonero.util.normalizeZip;
|
||||
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Strips non-reproducible data from MANIFEST files.
|
||||
* This stripper removes the following lines from the manifest:
|
||||
* - Built-By
|
||||
* - Created-By
|
||||
* - Build-Jdk
|
||||
* - Build-Date / Build-Time
|
||||
* - Bnd-LastModified
|
||||
* It also ensures that the MANIFEST entries are in a reproducible order
|
||||
* (workaround for MSHARED-511 that was fixed in maven-archiver-3.0.1).
|
||||
*/
|
||||
public final class ManifestStripper implements Stripper {
|
||||
private static final String[] DEFAULT_ATTRIBUTES =
|
||||
{"Built-By", "Created-By", "Build-Jdk", "Build-Date", "Build-Time",
|
||||
"Bnd-LastModified", "OpenIDE-Module-Build-Version"};
|
||||
private final List<String> manifestAttributes;
|
||||
|
||||
/**
|
||||
* Creates a stripper that will remove a default list of manifest attributes.
|
||||
*/
|
||||
public ManifestStripper() {
|
||||
this.manifestAttributes = new ArrayList<String>(Arrays.asList(DEFAULT_ATTRIBUTES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a stripper that will remove a default list of manifest attributes
|
||||
* plus additional user-specified attributes.
|
||||
*
|
||||
* @param manifestAttributes additional attributes to strip.
|
||||
*/
|
||||
public ManifestStripper(List<String> manifestAttributes) {
|
||||
this();
|
||||
if (manifestAttributes != null) {
|
||||
this.manifestAttributes.addAll(manifestAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void strip(File in, File out) throws IOException {
|
||||
final TextFileStripper s1 = new TextFileStripper();
|
||||
manifestAttributes.forEach(att -> s1.addPredicate(s -> s.startsWith(att + ":")));
|
||||
final SortManifestFileStripper s2 = new SortManifestFileStripper();
|
||||
new CompoundStripper(s1, s2).strip(in, out);
|
||||
}
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
package org.getmonero.util.normalizeZip;/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Based on: https://github.com/Zlika/reproducible-build-maven-plugin/blob/master/src/main/java/io/github/zlika/reproducible/ZipStripper.java
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.X5455_ExtendedTimestamp;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||
import org.apache.commons.compress.archivers.zip.ZipExtraField;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* Strips non-reproducible data from a ZIP file.
|
||||
* It rebuilds the ZIP file with a predictable order for the zip entries and sets zip entry dates to a fixed value.
|
||||
*/
|
||||
public final class NormalizeZip {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if(args.length!=2) {
|
||||
System.out.println("Arguments: timestampSource zip");
|
||||
System.exit(1);
|
||||
}
|
||||
File timestampSourceFile = Path.of(args[0]).toFile();
|
||||
File sourceZipFile = Path.of(args[1]).toFile();
|
||||
File destZipFile = Path.of(args[1]+".tmp").toFile();
|
||||
|
||||
if(!timestampSourceFile.exists()) {
|
||||
System.out.println("Timestamp source file does not exist");
|
||||
System.exit(1);
|
||||
}
|
||||
if(!sourceZipFile.exists()) {
|
||||
System.out.println("Source zip file does not exist");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
System.out.println("Normalizing zip " + sourceZipFile.getCanonicalPath() + " to timestamp: " + new Date(timestampSourceFile.lastModified()));
|
||||
|
||||
NormalizeZip nz = new NormalizeZip(timestampSourceFile.lastModified(), true).addFileStripper("META-INF/MANIFEST.MF", new ManifestStripper());
|
||||
nz.strip(sourceZipFile, destZipFile);
|
||||
if(!sourceZipFile.delete()) System.out.println("Cannot delete source file");
|
||||
if(!destZipFile.renameTo(sourceZipFile)) System.out.println("Cannot rename temporary zip file");
|
||||
destZipFile = sourceZipFile;
|
||||
if(!destZipFile.setLastModified(timestampSourceFile.lastModified())) System.out.println("Cannot update zip last modified date");
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator used to sort the files in the ZIP file.
|
||||
* This is mostly an alphabetical order comparator, with the exception that
|
||||
* META-INF/MANIFEST.MF and META-INF/ must be the 2 first entries (if they exist)
|
||||
* because this is required by some tools
|
||||
* (cf. https://github.com/Zlika/reproducible-build-maven-plugin/issues/16).
|
||||
*/
|
||||
private static final Comparator<String> MANIFEST_FILE_SORT_COMPARATOR = new Comparator<String>() {
|
||||
// CHECKSTYLE IGNORE LINE: ReturnCount
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
if ("META-INF/MANIFEST.MF".equals(o1)) {
|
||||
return -1;
|
||||
}
|
||||
if ("META-INF/MANIFEST.MF".equals(o2)) {
|
||||
return 1;
|
||||
}
|
||||
if ("META-INF/".equals(o1)) {
|
||||
return -1;
|
||||
}
|
||||
if ("META-INF/".equals(o2)) {
|
||||
return 1;
|
||||
}
|
||||
return o1.compareTo(o2);
|
||||
}
|
||||
};
|
||||
|
||||
private final long zipTimestamp;
|
||||
private final boolean fixZipExternalFileAttributes;
|
||||
|
||||
|
||||
public NormalizeZip(long zipTimestamp, boolean fixZipExternalFileAttributes) {
|
||||
this.zipTimestamp = zipTimestamp;
|
||||
this.fixZipExternalFileAttributes = fixZipExternalFileAttributes;
|
||||
}
|
||||
|
||||
private final Map<String, Stripper> subFilters = new HashMap<>();
|
||||
private Stripper getSubFilter(String name) {
|
||||
for (Map.Entry<String, Stripper> filter : subFilters.entrySet()) {
|
||||
if (name.matches(filter.getKey())) {
|
||||
return filter.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Adds a stripper for a given file in the Zip.
|
||||
* @param filename the name of the file in the Zip (regular expression).
|
||||
* @param stripper the stripper to apply on the file.
|
||||
* @return this object (for method chaining).
|
||||
*/
|
||||
public NormalizeZip addFileStripper(String filename, Stripper stripper) {
|
||||
subFilters.put(filename, stripper);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public void strip(File in, File out) throws IOException {
|
||||
try (final ZipFile zip = new ZipFile(in);
|
||||
final ZipArchiveOutputStream zout = new ZipArchiveOutputStream(out)) {
|
||||
final List<String> sortedNames = sortEntriesByName(zip.getEntries());
|
||||
for (String name : sortedNames) {
|
||||
final ZipArchiveEntry entry = zip.getEntry(name);
|
||||
// Strip Zip entry
|
||||
final ZipArchiveEntry strippedEntry = filterZipEntry(entry);
|
||||
// Fix external file attributes if required
|
||||
if (in.getName().endsWith(".jar") || in.getName().endsWith(".war")) {
|
||||
fixAttributes(strippedEntry);
|
||||
}
|
||||
|
||||
// Strip file if required
|
||||
final Stripper stripper = getSubFilter(name);
|
||||
if (stripper != null)
|
||||
{
|
||||
// Unzip entry to temp file
|
||||
final File tmp = File.createTempFile("tmp", null);
|
||||
tmp.deleteOnExit();
|
||||
Files.copy(zip.getInputStream(entry), tmp.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
final File tmp2 = File.createTempFile("tmp", null);
|
||||
tmp2.deleteOnExit();
|
||||
stripper.strip(tmp, tmp2);
|
||||
final byte[] fileContent = Files.readAllBytes(tmp2.toPath());
|
||||
strippedEntry.setSize(fileContent.length);
|
||||
zout.putArchiveEntry(strippedEntry);
|
||||
zout.write(fileContent);
|
||||
zout.closeArchiveEntry();
|
||||
}
|
||||
else {
|
||||
// Copy the Zip entry as-is
|
||||
zout.addRawArchiveEntry(strippedEntry, zip.getRawInputStream(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fixAttributes(ZipArchiveEntry entry) {
|
||||
if (fixZipExternalFileAttributes) {
|
||||
/* ZIP external file attributes:
|
||||
TTTTsstrwxrwxrwx0000000000ADVSHR
|
||||
^^^^____________________________ file type
|
||||
(file: 1000 , dir: 0100)
|
||||
^^^_________________________ setuid, setgid, sticky
|
||||
^^^^^^^^^________________ Unix permissions
|
||||
^^^^^^ DOS attributes
|
||||
The argument of setUnixMode() only takes the 2 upper bytes. */
|
||||
if (entry.isDirectory()) {
|
||||
entry.setUnixMode((0b0100 << 12) + 0755);
|
||||
} else {
|
||||
entry.setUnixMode((0b1000 << 12) + 0644);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> sortEntriesByName(Enumeration<ZipArchiveEntry> entries) {
|
||||
return Collections.list(entries).stream()
|
||||
.map(e -> e.getName())
|
||||
.sorted(MANIFEST_FILE_SORT_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ZipArchiveEntry filterZipEntry(ZipArchiveEntry entry) {
|
||||
// Set times
|
||||
entry.setCreationTime(FileTime.fromMillis(zipTimestamp));
|
||||
entry.setLastAccessTime(FileTime.fromMillis(zipTimestamp));
|
||||
entry.setLastModifiedTime(FileTime.fromMillis(zipTimestamp));
|
||||
entry.setTime(zipTimestamp);
|
||||
// Remove extended timestamps
|
||||
for (ZipExtraField field : entry.getExtraFields()) {
|
||||
if (field instanceof X5455_ExtendedTimestamp) {
|
||||
entry.removeExtraField(field.getHeaderId());
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package org.getmonero.util.normalizeZip;
|
||||
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Sorts a MANIFEST file by attribute.
|
||||
*/
|
||||
final class SortManifestFileStripper implements Stripper {
|
||||
private static final Comparator<String> MANIFEST_ENTRY_COMPARATOR = new Comparator<String>() {
|
||||
// CHECKSTYLE IGNORE LINE: ReturnCount
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
if (o1.startsWith("Manifest-Version:")) {
|
||||
return -1;
|
||||
} else if (o2.startsWith("Manifest-Version:")) {
|
||||
return 1;
|
||||
}
|
||||
// From the "JAR File Specification":
|
||||
// Each section must start with an attribute with the name as "Name"
|
||||
else if (o1.startsWith("Name:") && !o2.startsWith("Name:")) {
|
||||
return -1;
|
||||
} else if (o2.startsWith("Name:") && !o1.startsWith("Name:")) {
|
||||
return 1;
|
||||
} else {
|
||||
return o1.compareTo(o2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void strip(File in, File out) throws IOException {
|
||||
final List<String> lines = Files.readAllLines(in.toPath(), StandardCharsets.UTF_8);
|
||||
|
||||
try (final BufferedWriter writer = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(out), StandardCharsets.UTF_8))) {
|
||||
final String sortedManifest = sortManifestSections(lines).stream()
|
||||
.collect(Collectors.joining("\r\n"));
|
||||
try {
|
||||
writer.write(sortedManifest + "\r\n");
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> sortManifestSections(List<String> lines) {
|
||||
final List<List<String>> sections = new ArrayList<>();
|
||||
List<String> currentSection = new ArrayList<>();
|
||||
for (String line : lines) {
|
||||
// New section?
|
||||
if (line.isEmpty()) {
|
||||
if (!currentSection.isEmpty()) {
|
||||
sections.add(currentSection);
|
||||
currentSection = new ArrayList<>();
|
||||
}
|
||||
} else {
|
||||
currentSection.add(line);
|
||||
}
|
||||
}
|
||||
if (!currentSection.isEmpty()) {
|
||||
sections.add(currentSection);
|
||||
}
|
||||
|
||||
return sections.stream()
|
||||
.map(list -> sortAttributes(list))
|
||||
.map(list -> String.join("", list))
|
||||
.sorted(MANIFEST_ENTRY_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<String> sortAttributes(List<String> lines) {
|
||||
final List<String> attributes = new ArrayList<>();
|
||||
String currentAttribute = "";
|
||||
for (String line : lines) {
|
||||
if (line.startsWith(" ")) {
|
||||
currentAttribute += line + "\r\n";
|
||||
} else {
|
||||
if (!currentAttribute.isEmpty()) {
|
||||
attributes.add(currentAttribute);
|
||||
currentAttribute = "";
|
||||
}
|
||||
currentAttribute = line + "\r\n";
|
||||
}
|
||||
}
|
||||
if (!currentAttribute.isEmpty()) {
|
||||
attributes.add(currentAttribute);
|
||||
}
|
||||
attributes.sort(MANIFEST_ENTRY_COMPARATOR);
|
||||
return attributes;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package org.getmonero.util.normalizeZip;
|
||||
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Generic interface for stripping non-reproducible data.
|
||||
*/
|
||||
public interface Stripper
|
||||
{
|
||||
/**
|
||||
* Strips non-reproducible data.
|
||||
* @param in the input file.
|
||||
* @param out the stripped output file.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
void strip(File in, File out) throws IOException;
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package org.getmonero.util.normalizeZip;
|
||||
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Generic text file stripper.
|
||||
*/
|
||||
class TextFileStripper implements Stripper {
|
||||
private final List<Predicate<String>> predicates = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Adds a predicate to filter the text file.
|
||||
*
|
||||
* @param predicate the predicate.
|
||||
* @return this.
|
||||
*/
|
||||
public TextFileStripper addPredicate(Predicate<String> predicate) {
|
||||
predicates.add(predicate.negate());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void strip(File in, File out) throws IOException {
|
||||
try (final BufferedWriter writer = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(out), StandardCharsets.UTF_8));
|
||||
final BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(new FileInputStream(in), StandardCharsets.UTF_8))) {
|
||||
reader.lines().filter(s -> predicates.stream().allMatch(p -> p.test(s)))
|
||||
.forEach(s ->
|
||||
{
|
||||
try {
|
||||
writer.write(s);
|
||||
writer.write("\r\n");
|
||||
} catch (IOException e) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue