Created
January 31, 2023 06:01
-
-
Save jmini/222e9454667ff0da2e099f05aef0dfa2 to your computer and use it in GitHub Desktop.
Format MANIFEST.MF files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///usr/bin/env jbang "$0" "$@" ; exit $? | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.IOException; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.Comparator; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Map.Entry; | |
import java.util.Objects; | |
import java.util.jar.Attributes; | |
import java.util.jar.Attributes.Name; | |
import java.util.jar.Manifest; | |
import java.util.stream.Stream; | |
public class FormatManifestMain { | |
private static final Comparator<Name> nameComparator = Comparator.comparing(Name::toString, String.CASE_INSENSITIVE_ORDER); | |
private static final Name NAME = new Name("Name"); | |
private static final String EOL = "\r\n"; | |
private static final String SEPARATOR = ": "; | |
private static final int CHAR = 1; | |
private static final int DELIMITER = 2; | |
private static final int STARTQUOTE = 4; | |
private static final int ENDQUOTE = 8; | |
public static void main(String... args) throws IOException { | |
if (args.length != 1) { | |
throw new IllegalStateException("jbang " + FormatManifestMain.class.getSimpleName() + " <manifest file name>"); | |
} | |
System.out.println("File: " + args[0]); | |
File file = new File(args[0]); | |
if (!"MANIFEST.MF".equals(file.getName())) { | |
throw new IllegalStateException("File name is not expected: " + file.getAbsolutePath()); | |
} | |
System.out.println(); | |
Manifest manifest = new Manifest(new FileInputStream(file)); | |
String content = humanReadableContent(manifest); | |
System.out.println(content); | |
} | |
// Inspired from aQute.lib.manifest.ManifestUtil.write(..) in the bnd project | |
// See https://github.com/bndtools/bnd/blob/master/aQute.libg/src/aQute/lib/manifest/ManifestUtil.java#L50 | |
public static String humanReadableContent(Manifest manifest) throws IOException { | |
StringBuilder sb = new StringBuilder(); | |
Attributes mainAttributes = manifest.getMainAttributes(); | |
Stream<Entry<Name, String>> sortedAttributes = sortedAttributes(mainAttributes); | |
Name versionName = Name.MANIFEST_VERSION; | |
String versionValue = mainAttributes.getValue(versionName); | |
if (versionValue == null) { | |
versionName = Name.SIGNATURE_VERSION; | |
versionValue = mainAttributes.getValue(versionName); | |
} | |
if (versionValue != null) { | |
writeEntry(sb, versionName, versionValue); | |
Name filterName = versionName; | |
// Name.equals is case-insensitive | |
sortedAttributes = sortedAttributes.filter(e -> !Objects.equals(e.getKey(), filterName)); | |
} | |
writeAttributes(sb, sortedAttributes); | |
sb.append(EOL); | |
for (Iterator<Entry<String, Attributes>> iterator = manifest.getEntries() | |
.entrySet() | |
.stream() | |
.sorted(Entry.comparingByKey()) | |
.iterator(); iterator.hasNext();) { | |
Entry<String, Attributes> entry = iterator.next(); | |
writeEntry(sb, NAME, entry.getKey()); | |
writeAttributes(sb, sortedAttributes(entry.getValue())); | |
sb.append(EOL); | |
} | |
return sb.toString(); | |
} | |
private static void writeEntry(StringBuilder sb, Name name, String value) throws IOException { | |
sb.append(name.toString()); | |
sb.append(SEPARATOR); | |
List<String> values = parseDelimitedString(value, ","); | |
if (values.size() == 1) { | |
sb.append(value); | |
} else { | |
String multilineValue = String.join(",\r\n ", values); | |
sb.append(multilineValue); | |
} | |
sb.append(EOL); | |
} | |
private static void writeAttributes(StringBuilder sb, Stream<Entry<Name, String>> attributes) throws IOException { | |
for (Iterator<Entry<Name, String>> iterator = attributes.iterator(); iterator.hasNext();) { | |
Entry<Name, String> attribute = iterator.next(); | |
writeEntry(sb, attribute.getKey(), attribute.getValue()); | |
} | |
} | |
private static Stream<Entry<Name, String>> sortedAttributes(Attributes attributes) { | |
return coerce(attributes).entrySet() | |
.stream() | |
.sorted(Entry.comparingByKey(nameComparator)); | |
} | |
@SuppressWarnings({ "unchecked", "rawtypes" }) | |
private static Map<Name, String> coerce(Attributes attributes) { | |
return (Map) attributes; | |
} | |
// Inspired from org.apache.felix.utils.resource.ResourceBuilder.parseDelimitedString(..) in the felix project | |
// See https://github.com/apache/felix-dev/blob/master/utils/src/main/java/org/apache/felix/utils/resource/ResourceBuilder.java | |
private static List<String> parseDelimitedString(String value, String delim) { | |
if (value == null) { | |
return Collections.emptyList(); | |
} | |
List<String> list = new ArrayList<>(); | |
StringBuilder sb = new StringBuilder(); | |
int expecting = (CHAR | DELIMITER | STARTQUOTE); | |
for (int i = 0; i < value.length(); i++) { | |
char c = value.charAt(i); | |
boolean isDelimiter = (delim.indexOf(c) >= 0); | |
boolean isQuote = (c == '"'); | |
if (isDelimiter && ((expecting & DELIMITER) > 0)) { | |
list.add(sb.toString() | |
.trim()); | |
sb.delete(0, sb.length()); | |
expecting = (CHAR | DELIMITER | STARTQUOTE); | |
} else if (isQuote && ((expecting & STARTQUOTE) > 0)) { | |
sb.append(c); | |
expecting = CHAR | ENDQUOTE; | |
} else if (isQuote && ((expecting & ENDQUOTE) > 0)) { | |
sb.append(c); | |
expecting = (CHAR | STARTQUOTE | DELIMITER); | |
} else if ((expecting & CHAR) > 0) { | |
sb.append(c); | |
} else { | |
throw new IllegalArgumentException("Invalid delimited string: " + value); | |
} | |
} | |
String s = sb.toString() | |
.trim(); | |
if (s.length() > 0) { | |
list.add(s); | |
} | |
return Collections.unmodifiableList(list); | |
} | |
private FormatManifestMain() { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In the bnd project the method in the ManifestUtil is serializing the content of a java.util.jar.Manifest object according to the spec (72 chars per line…)
This method is serializing the content in an human readable way.
To run the script, you can use jbang: