Skip to content

Commit 597f072

Browse files
authored
Add ProGuard mapping writer (#15)
* Add ProGuard mapping writer * Add missing license header * Use Writer instead of BufferedWriter * Move the simplest constructor first * Add checks for nulls and invalid namespaces * Actually call the destination namespace 'dstNamespace' in names * Make dstNamespaceString final
1 parent 14a5f72 commit 597f072

File tree

2 files changed

+241
-0
lines changed

2 files changed

+241
-0
lines changed

.editorconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[*.{gradle,java}]
2+
indent_style = tab
3+
ij_continuation_indent_size = 8
4+
ij_java_imports_layout = $*,|,java.**,|,javax.**,|,*,|,net.fabricmc.**
5+
ij_java_class_count_to_use_import_on_demand = 999
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/*
2+
* Copyright (c) 2022 FabricMC
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+
17+
package net.fabricmc.mappingio.format;
18+
19+
import java.io.IOException;
20+
import java.io.Writer;
21+
import java.util.List;
22+
import java.util.Objects;
23+
24+
import org.objectweb.asm.Type;
25+
26+
import net.fabricmc.mappingio.MappedElementKind;
27+
import net.fabricmc.mappingio.MappingWriter;
28+
29+
/**
30+
* A mapping writer for the ProGuard mapping format.
31+
* Note that this format is very basic: it only supports
32+
* one namespace pair and only classes, methods and fields
33+
* without comments.
34+
*
35+
* @see <a href="https://www.guardsquare.com/manual/tools/retrace">Official format documentation</a>
36+
*/
37+
public final class ProGuardWriter implements MappingWriter {
38+
private final Writer writer;
39+
private int dstNamespace = -1;
40+
private final String dstNamespaceString;
41+
42+
/**
43+
* Constructs a ProGuard mapping writer that uses
44+
* the first destination namespace (index 0).
45+
*
46+
* @param writer the writer where the mappings will be written
47+
*/
48+
public ProGuardWriter(Writer writer) {
49+
this(writer, 0);
50+
}
51+
52+
/**
53+
* Constructs a ProGuard mapping writer.
54+
*
55+
* @param writer the writer where the mappings will be written
56+
* @param dstNamespace the namespace index to write as the destination namespace, must be at least 0
57+
*/
58+
public ProGuardWriter(Writer writer, int dstNamespace) {
59+
this.writer = Objects.requireNonNull(writer, "writer cannot be null");
60+
this.dstNamespace = dstNamespace;
61+
this.dstNamespaceString = null;
62+
63+
if (dstNamespace < 0) {
64+
throw new IllegalArgumentException("Namespace must be non-negative, found " + dstNamespace);
65+
}
66+
}
67+
68+
/**
69+
* Constructs a ProGuard mapping writer.
70+
*
71+
* @param writer the writer where the mappings will be written
72+
* @param dstNamespace the namespace name to write as the destination namespace
73+
*/
74+
public ProGuardWriter(Writer writer, String dstNamespace) {
75+
this.writer = Objects.requireNonNull(writer, "writer cannot be null");
76+
this.dstNamespaceString = Objects.requireNonNull(dstNamespace, "namespace cannot be null");
77+
}
78+
79+
/**
80+
* Closes the internal {@link Writer}.
81+
*
82+
* @throws IOException if an IO error occurs
83+
*/
84+
@Override
85+
public void close() throws IOException {
86+
writer.close();
87+
}
88+
89+
@Override
90+
public void visitNamespaces(String srcNamespace, List<String> dstNamespaces) throws IOException {
91+
if (dstNamespaceString != null) {
92+
dstNamespace = dstNamespaces.indexOf(dstNamespaceString);
93+
94+
if (dstNamespace == -1) {
95+
throw new RuntimeException("Invalid destination namespace '" + dstNamespaceString + "' not in [" + String.join(", ", dstNamespaces) + ']');
96+
}
97+
}
98+
99+
if (dstNamespace >= dstNamespaces.size()) {
100+
throw new IndexOutOfBoundsException("Namespace " + dstNamespace + " doesn't exist in [" + String.join(", ", dstNamespaces) + ']');
101+
}
102+
}
103+
104+
@Override
105+
public boolean visitClass(String srcName) throws IOException {
106+
writer.write(toJavaClassName(srcName));
107+
writeArrow();
108+
return true;
109+
}
110+
111+
@Override
112+
public boolean visitField(String srcName, String srcDesc) throws IOException {
113+
writeIndent();
114+
writer.write(toJavaType(srcDesc));
115+
writer.write(' ');
116+
writer.write(srcName);
117+
writeArrow();
118+
return true;
119+
}
120+
121+
@Override
122+
public boolean visitMethod(String srcName, String srcDesc) throws IOException {
123+
Type type = Type.getMethodType(srcDesc);
124+
writeIndent();
125+
writer.write(toJavaType(type.getReturnType().getDescriptor()));
126+
writer.write(' ');
127+
writer.write(srcName);
128+
writer.write('(');
129+
Type[] args = type.getArgumentTypes();
130+
131+
for (int i = 0; i < args.length; i++) {
132+
if (i > 0) {
133+
writer.write(',');
134+
}
135+
136+
writer.write(toJavaType(args[i].getDescriptor()));
137+
}
138+
139+
writer.write(')');
140+
writeArrow();
141+
return true;
142+
}
143+
144+
@Override
145+
public boolean visitMethodArg(int argPosition, int lvIndex, String srcName) throws IOException {
146+
// ignored
147+
return false;
148+
}
149+
150+
@Override
151+
public boolean visitMethodVar(int lvtRowIndex, int lvIndex, int startOpIdx, String srcName) throws IOException {
152+
// ignored
153+
return false;
154+
}
155+
156+
@Override
157+
public void visitDstName(MappedElementKind targetKind, int namespace, String name) throws IOException {
158+
if (this.dstNamespace != namespace) {
159+
return;
160+
}
161+
162+
if (targetKind == MappedElementKind.CLASS) {
163+
writer.write(toJavaClassName(name));
164+
writer.write(':');
165+
} else {
166+
writer.write(name);
167+
}
168+
169+
writer.write('\n');
170+
}
171+
172+
@Override
173+
public void visitComment(MappedElementKind targetKind, String comment) throws IOException {
174+
// ignored
175+
}
176+
177+
private void writeArrow() throws IOException {
178+
writer.write(" -> ");
179+
}
180+
181+
private void writeIndent() throws IOException {
182+
// This has to be exactly 4 spaces.
183+
writer.write(" ");
184+
}
185+
186+
/**
187+
* Replaces the slashes as package separators with dots
188+
* since ProGuard uses Java-like dotted class names.
189+
*/
190+
private static String toJavaClassName(String name) {
191+
return name.replace('/', '.');
192+
}
193+
194+
private static String toJavaType(String descriptor) {
195+
StringBuilder result = new StringBuilder();
196+
int arrayLevel = 0;
197+
198+
for (int i = 0; i < descriptor.length(); i++) {
199+
switch (descriptor.charAt(i)) {
200+
case '[': arrayLevel++; break;
201+
case 'B': result.append("byte"); break;
202+
case 'S': result.append("short"); break;
203+
case 'I': result.append("int"); break;
204+
case 'J': result.append("long"); break;
205+
case 'F': result.append("float"); break;
206+
case 'D': result.append("double"); break;
207+
case 'C': result.append("char"); break;
208+
case 'Z': result.append("boolean"); break;
209+
case 'V': result.append("void"); break;
210+
case 'L':
211+
while (i + 1 < descriptor.length()) {
212+
char c = descriptor.charAt(++i);
213+
214+
if (c == '/') {
215+
result.append('.');
216+
} else if (c == ';') {
217+
break;
218+
} else {
219+
result.append(c);
220+
}
221+
}
222+
223+
break;
224+
default: throw new IllegalArgumentException("Unknown character in descriptor: " + descriptor.charAt(i));
225+
}
226+
}
227+
228+
// TODO: This can be replaced by String.repeat in modern Java
229+
while (arrayLevel > 0) {
230+
result.append("[]");
231+
arrayLevel--;
232+
}
233+
234+
return result.toString();
235+
}
236+
}

0 commit comments

Comments
 (0)