summaryrefslogtreecommitdiff
path: root/test/jetty/RelocateJettyPackages.java
blob: 0766388e528aab871b2ec28f917ba57e14d225fc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
 * Proof-of-concept bytecode relocator for the Jetty 12 migration.
 *
 * This is deliberately a small build tool, not YaCy runtime code. It keeps
 * Solr's public packages unchanged while moving its private Jetty 9 linkage
 * below net.yacy.solr9.jetty.
 */
package net.yacy.test.jetty;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.commons.Remapper;

public final class RelocateJettyPackages {

    private static final String SOURCE_INTERNAL = "org/eclipse/jetty";
    private static final String TARGET_INTERNAL = "net/yacy/solr9/jetty";
    private static final String SOURCE_BINARY = "org.eclipse.jetty";
    private static final String TARGET_BINARY = "net.yacy.solr9.jetty";

    private RelocateJettyPackages() {
    }

    public static void main(final String[] args) throws IOException {
        if (args.length != 2) {
            throw new IllegalArgumentException("usage: RelocateJettyPackages INPUT.jar OUTPUT.jar");
        }
        relocate(Path.of(args[0]), Path.of(args[1]));
    }

    private static void relocate(final Path input, final Path output) throws IOException {
        Files.createDirectories(output.toAbsolutePath().getParent());
        final Remapper remapper = new JettyRemapper();

        try (JarFile source = new JarFile(input.toFile());
                JarOutputStream target = new JarOutputStream(Files.newOutputStream(output))) {
            final Enumeration<JarEntry> entries = source.entries();
            while (entries.hasMoreElements()) {
                final JarEntry entry = entries.nextElement();
                if (entry.isDirectory() || isSignature(entry.getName())) {
                    continue;
                }
                final String outputName = relocateEntryName(entry.getName());
                final JarEntry outputEntry = new JarEntry(outputName);
                outputEntry.setTime(entry.getTime());
                target.putNextEntry(outputEntry);
                try (InputStream stream = source.getInputStream(entry)) {
                    final byte[] content = readAll(stream);
                    if (entry.getName().endsWith(".class")) {
                        target.write(relocateClass(content, remapper));
                    } else if (entry.getName().startsWith("META-INF/services/")) {
                        target.write(new String(content, StandardCharsets.UTF_8)
                                .replace(SOURCE_BINARY, TARGET_BINARY)
                                .getBytes(StandardCharsets.UTF_8));
                    } else {
                        target.write(content);
                    }
                }
                target.closeEntry();
            }
        }
    }

    private static final class JettyRemapper extends Remapper {

        private JettyRemapper() {
            super(Opcodes.ASM9);
        }

        @Override
        public String map(final String internalName) {
            return replacePrefix(internalName, SOURCE_INTERNAL, TARGET_INTERNAL);
        }

        @Override
        public Object mapValue(final Object value) {
            if (value instanceof String) {
                final String text = (String) value;
                return text.replace(SOURCE_BINARY, TARGET_BINARY)
                        .replace(SOURCE_INTERNAL, TARGET_INTERNAL);
            }
            return super.mapValue(value);
        }
    }

    private static byte[] relocateClass(final byte[] content, final Remapper remapper) {
        final ClassReader reader = new ClassReader(content);
        final ClassWriter writer = new ClassWriter(0);
        reader.accept(new ClassRemapper(writer, remapper), 0);
        return writer.toByteArray();
    }

    private static boolean isSignature(final String name) {
        final String upper = name.toUpperCase(java.util.Locale.ROOT);
        return upper.startsWith("META-INF/")
                && (upper.endsWith(".SF") || upper.endsWith(".RSA") || upper.endsWith(".DSA"));
    }

    private static String replacePrefix(final String value, final String source, final String target) {
        if (value.equals(source)) {
            return target;
        }
        if (value.startsWith(source + "/")) {
            return target + value.substring(source.length());
        }
        return value;
    }

    private static String relocateEntryName(final String name) {
        final String internalName = replacePrefix(name, SOURCE_INTERNAL, TARGET_INTERNAL);
        if (internalName.startsWith("META-INF/services/")) {
            return internalName.replace(SOURCE_BINARY, TARGET_BINARY);
        }
        return internalName;
    }

    private static byte[] readAll(final InputStream stream) throws IOException {
        final ByteArrayOutputStream output = new ByteArrayOutputStream();
        stream.transferTo(output);
        return output.toByteArray();
    }
}