Skip to content

Commit 69ff1cf

Browse files
committed
Merge branch 'release-0.5.4'
2 parents c6e3cc3 + f2f1445 commit 69ff1cf

31 files changed

+2533
-215
lines changed

build.gradle

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ subprojects {
2020

2121
group = 'org.cadixdev'
2222
archivesBaseName = project.name.toLowerCase()
23-
version = '0.5.3'
23+
version = '0.5.4'
2424

2525
repositories {
2626
mavenCentral()
@@ -48,6 +48,20 @@ subprojects {
4848
from 'LICENSE.txt'
4949
}
5050

51+
javadoc {
52+
def javadocArgsFile = project.file("${project.buildDir}/tmp/javadoc-tags.options")
53+
doFirst {
54+
javadocArgsFile.parentFile.mkdirs()
55+
// These tags aren't supported by default, though they are standard in the JDK
56+
// (and most tools, including IntelliJ, understand them by default)
57+
javadocArgsFile.write("""-tag "apiNote:a:API Note:"
58+
-tag "implSpec:a:Implementation Requirements:"
59+
-tag "implNote:a:Implementation Note:"
60+
""")
61+
}
62+
options.optionFiles(javadocArgsFile)
63+
}
64+
5165
task javadocJar(type: Jar, dependsOn: 'javadoc') {
5266
from javadoc.destinationDir
5367
classifier = 'javadoc'

changelogs/0.5.3.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Thanks to @phase and @DemonWav for their contributions towards this release.
2626
identifier of registered values.
2727
- `Registry#entries()`, returning a `Set` of `Map.Entry`s.
2828

29-
### Imrpoved Mapping Merging
29+
### Improved Mapping Merging
3030

3131
Mapping Merging, introduced with Lorenz 0.5.0, has always been a fairly
3232
lacklustre implementation - dropping mappings present on the b mapping set, if

changelogs/0.5.4.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
Lorenz 0.5.4
2+
============
3+
4+
Lorenz 0.5.4 includes yet-more bug fixes, and a new (and vastly more powerful)
5+
mapping merging API.
6+
7+
The ProGuard mapping reader now also reads field types.
8+
9+
The Bombe dependency has been bumped to 0.3.4, resolving a `NullPointerException`
10+
that would occur when using the `ClassLoaderClassProvider`.
11+
12+
Merging
13+
-------
14+
15+
Lorenz now has a new mappings merging API which aims at fulfilling 3 main goals:
16+
17+
1. Improve the output of the merging operation significantly, supporting many more edge cases and handling merges
18+
with less complete mapping sets.
19+
2. Allow configuring (to some degree) the merging process to control some aspects of the default merge process.
20+
3. Provide an API which is extensible and modifiable at multiple levels to enable custom merge situations.
21+
22+
There are a limited number of possible edge cases one would expect to run into when merging, so we laid them all out
23+
and made decisions on how best to handle them in the default case. Due to the extensible API users can override and
24+
change any of the default behavioe as necessary. The default behavior is laid out as follows:
25+
26+
|Left|Right|Output|Note|
27+
|----|-----|------|----|
28+
| `A -> B` | `B -> C` | `A -> C` | Typical case, easiest to handle |
29+
| `A -> B` | _Missing_ | `A -> B` | Standalone mappings get copied |
30+
| _Missing_ | `B -> C` | `B -> C` | Standalone mappings get copied |
31+
| `A -> B` | `X -> Y` | `A -> B` and `X -> Y` | This is no different than the 2 above cases with missing mappings on each side. This is just meant to be a further example that if two unrelated mappings are present, a standard merger won't know how to handle them other than copying both. |
32+
| `A -> B` | `A -> C` | `A -> C` | By default the right mapping is considered the "most up to date" mappings, so in the case where both mapping sets provide mappings for the same obfuscated name, the right mapping is used in the default implementation. |
33+
| `A -> B` (types) | `B -> B` (types) and `A -> C` (names) | `A -> B` (types) and `A -> C` (names) | This is an example of a special case situation where the left mapping only maps types, then the second mapping set only maps members, but from the expectation that the first mapping set was already applied. The default implementation should handle this case correctly. |
34+
35+
> How to read the above chart: Each mapping operation is made up of a `left` side and a `right` side. The chart
36+
> represents this with the directional arrow `->`. Merge operations in the table apply to any individual mapping,
37+
> not the whole mapping set. That is, a field name mapping merging with another field name, or a class name merging
38+
> with another class name.
39+
>
40+
> Handling merges with less complete mappings sets are what is intended by the last row.
41+
42+
### Directionality
43+
44+
It may seem a natural goal for merging mapping sets is for the merge operation to be commutative - in other words, when
45+
merging `A -> B` and `B -> C` you would get the same result as when merging `C -> B` and `B -> A` (except, reversed).
46+
47+
This is true for _most_ cases, but for some special cases - most notably duplicate mappings - this property just can't
48+
be handled in any reasonable way. In these situations where a decision has to be made regarding whether to keep one
49+
mapping or another (and neither being a better option by itself) we use the position of the mapping as the heuristic for
50+
which one to keep.
51+
52+
When merging `A -> B` and `B -> C` the names you get in the output are `C`. Because of this, the `right` side of a merge
53+
is considered the more correct set of names (since it is the output). If the merge operation were to be reversed `A`
54+
would be preferred instead. Because of this it is important to consider which direction works best for you merge.
55+
56+
### Other Scenarios
57+
58+
There's also 2 other factors necessary to consider when merging mapping sets that we'll go over:
59+
60+
1. Duplicate mappings are possible
61+
2. Field mappings can have type signature
62+
63+
#### Handling Duplicates
64+
65+
A duplicate mapping is when both mapping sets define a mapping for the same obfuscated member. For example, if you were
66+
to merge the following mappings, they would be considered duplicated because they both share `com.example.Example` as
67+
the obfuscated name.
68+
69+
```
70+
com.example.Example -> com.example.OtherExample1
71+
```
72+
```
73+
com.example.Example -> com.example.OtherExample2
74+
```
75+
76+
For the default merge implementation `right` mappings take priority for all duplicate merge scenarios. So in this case
77+
the mapping which would be present in the output mapping set would be:
78+
79+
```
80+
com.example.Example -> com.example.OtherExample2
81+
```
82+
83+
#### Handling Field Types
84+
85+
Field mappings can have types, but often don't. Because Java doesn't allow duplicate field names visible in any class
86+
it's not always necessary to explicitly list the type of the field. The complication comes when merging mapping sets
87+
where one mapping set does and the other doesn't contain field types. In this scenario looking up the field from one
88+
mapping set to another in the default case by simply using the field signature would fail, as the type descriptor is
89+
missing in one of the mapping sets.
90+
91+
The new merge system provides a configuration option for whether to handle this case. The default options is `LOOSE` -
92+
that is, first check for the signature, but also check for just the field name and use it if the field
93+
signature can't be found. This can be switched to `STRICT` where all field mappings must match signatures exactly.
94+
95+
### Merging Code and API
96+
97+
The new merging system is made up of 2 main classes:
98+
99+
1. [`MappingSetMerger`](https://github.com/CadixDev/Lorenz/blob/312c9ed737964b21a9058b29ec041831c955b537/lorenz/src/main/java/org/cadixdev/lorenz/merge/MappingSetMerger.java)
100+
2. [`MappingSetMergerHandler`](https://github.com/CadixDev/Lorenz/blob/312c9ed737964b21a9058b29ec041831c955b537/lorenz/src/main/java/org/cadixdev/lorenz/merge/MappingSetMergerHandler.java)
101+
102+
The JavaDocs for each class go into considerable detail on exactly what each class is and how it's used, but here's a
103+
quick rundown:
104+
105+
`MappingSetMerger` is the entry point for the merge operation. It's an interface with a default implementation which
106+
can be retrieved with `MappingSetMerger.create()`. It does the job of walking the class hierarchy of both mapping sets
107+
and finding the members of each side that should be merged together. It decides which members to include based on the
108+
[`MergeConfig`](https://github.com/CadixDev/Lorenz/blob/312c9ed737964b21a9058b29ec041831c955b537/lorenz/src/main/java/org/cadixdev/lorenz/merge/MergeConfig.java)
109+
provided in the call to `create()` (or just the default config if none was provided) and determines which individual
110+
case each merge is.
111+
112+
`MappingSetMerger` is available with an overridable default implementation in case it's necessary to modify how the
113+
above cases are decided individually. It is designed to let a user override individual methods in order to change said
114+
logic when necessary, but the general idea is that in most cases you'll never need to modify the default implementation
115+
of `MappingSetMerger`.
116+
117+
`MappingSetMerger` doesn't handle actually merging the mappings, all it does is recognize which type of merge each merge
118+
is, and delegates the handling of that merge to `MappingSetMergerHandler`. This handler class has individual methods for
119+
each mapping case, and is a fully-default interface. This means you can implement `MappingSetMergerHandler` and override
120+
only the mapping cases you want to modify for your custom merge operation. The merge cases are all described by the
121+
method names (and explained in further detail in the JavaDoc), and look like:
122+
123+
```java
124+
FieldMapping mergeFieldMappings(
125+
final FieldMapping left,
126+
final FieldMapping strictRight,
127+
final FieldMapping looseRight,
128+
final ClassMapping<?, ?> target,
129+
final MergeContext context
130+
);
131+
132+
FieldMapping mergeDuplicateFieldMappings(
133+
final FieldMapping left,
134+
final FieldMapping strictRightDuplicate,
135+
final FieldMapping looseRightDuplicate,
136+
final FieldMapping strictRightContinuation,
137+
final FieldMapping looseRightContinuation,
138+
final ClassMapping<?, ?> target,
139+
final MergeContext context
140+
);
141+
142+
FieldMapping addLeftFieldMapping(final FieldMapping left, final ClassMapping<?, ?> target, final MergeContext context);
143+
144+
FieldMapping addRightFieldMapping(final FieldMapping right, final ClassMapping<?, ?> target, final MergeContext context);
145+
```
146+
147+
If you only need some custom logic when the `right` mapping set provides a mapping that the `left` mapping set doesn't
148+
provide, then you could just override `addRightFieldMapping` with your custom logic and leave everything else default.
149+
You wouldn't need to worry about walking the hierarchy or copying of the other members or handling any other special
150+
cases.
151+
152+
This separation of concerns between determining what _kind_ of merge each case is and actually _handling_ the merge
153+
operation for each merge case hopefully will allow you to easily customize mapping merges for whatever custom situation
154+
you have.
155+
156+
#### Existing Code
157+
158+
Each of the existing `merge()` methods on each of the mapping classes have all been updated to use this new merge
159+
system. The methods haven't been removed and are still binary compatible with any existing code you have which may be
160+
using it. The output of the merge may differ however, as the logic has been considerably modified as described above.
161+
162+
#### One Last Thing
163+
164+
The default implementation of `MappingSetMerger` merges top-level classes in parallel. This is okay because
165+
`MappingSetMergerHandler` is a stateless class, and `MappingSetMerger` is completely thread-safe. Due to this merge time
166+
fell by as much as half of the previous merge time when testing against Minecraft mapping sets.

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ url = https://www.jamiemansfield.me/projects/lorenz
44
inceptionYear = 2016
55

66
# Build Settings
7-
bombeVersion = 0.3.2
7+
bombeVersion = 0.3.4

lorenz-io-proguard/src/main/java/org/cadixdev/lorenz/io/proguard/PGTypeReader.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,34 @@
3333
import org.cadixdev.bombe.type.Type;
3434
import org.cadixdev.bombe.type.VoidType;
3535

36+
/**
37+
* A {@link StringReader reader} for {@link Type types} within the ProGuard
38+
* mapping format.
39+
*
40+
* @author Jamie Mansfield
41+
* @since 0.5.1
42+
*/
3643
public class PGTypeReader extends StringReader {
3744

3845
public PGTypeReader(final String source) {
3946
super(source);
4047
}
4148

49+
/**
50+
* Reads a {@link Type type} from the source.
51+
*
52+
* @return A type
53+
*/
4254
public Type readType() {
4355
if (this.match("void")) return VoidType.INSTANCE;
4456
return this.readFieldType();
4557
}
4658

59+
/**
60+
* Reads a {@link FieldType field type} from the source.
61+
*
62+
* @return A field type
63+
*/
4764
public FieldType readFieldType() {
4865
while (this.available() && this.peek() != '[') {
4966
this.advance();

lorenz-io-proguard/src/main/java/org/cadixdev/lorenz/io/proguard/ProGuardFormat.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333
import java.io.Writer;
3434
import java.util.Optional;
3535

36+
/**
37+
* The ProGuard mapping format.
38+
*
39+
* @author Jamie Mansfield
40+
* @since 0.5.1
41+
*/
3642
public class ProGuardFormat implements TextMappingFormat {
3743

3844
@Override

lorenz-io-proguard/src/main/java/org/cadixdev/lorenz/io/proguard/ProGuardReader.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
import org.cadixdev.bombe.type.FieldType;
2929
import org.cadixdev.bombe.type.MethodDescriptor;
3030
import org.cadixdev.bombe.type.Type;
31+
import org.cadixdev.bombe.type.signature.FieldSignature;
3132
import org.cadixdev.lorenz.MappingSet;
33+
import org.cadixdev.lorenz.io.MappingsReader;
3234
import org.cadixdev.lorenz.io.TextMappingsReader;
3335
import org.cadixdev.lorenz.model.ClassMapping;
3436

@@ -37,20 +39,21 @@
3739
import java.util.List;
3840
import java.util.stream.Collectors;
3941

42+
/**
43+
* An implementation of {@link MappingsReader} for the ProGuard format.
44+
*
45+
* @author Jamie Mansfield
46+
* @since 0.5.1
47+
*/
4048
public class ProGuardReader extends TextMappingsReader {
4149

4250
public ProGuardReader(final Reader reader) {
4351
super(reader, Processor::new);
4452
}
4553

46-
@Override
47-
public MappingSet read(final MappingSet mappings) {
48-
return super.read(mappings);
49-
}
50-
5154
public static class Processor extends TextMappingsReader.Processor {
5255

53-
private ClassMapping currentClass;
56+
private ClassMapping<?, ?> currentClass;
5457

5558
public Processor(final MappingSet mappings) {
5659
super(mappings);
@@ -99,7 +102,8 @@ public void accept(final String raw) {
99102
}
100103
// field
101104
else {
102-
this.currentClass.getOrCreateFieldMapping(obf)
105+
final FieldSignature fieldSignature = new FieldSignature(obf, new PGTypeReader(returnTypeRaw).readFieldType());
106+
this.currentClass.getOrCreateFieldMapping(fieldSignature)
103107
.setDeobfuscatedName(deobf);
104108
}
105109
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* This file is part of Lorenz, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) Jamie Mansfield <https://www.jamierocks.uk/>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
26+
/**
27+
* The Lorenz-provided implementation of the
28+
* <a href="https://www.guardsquare.com/en/products/proguard">ProGuard</a> mapping.txt
29+
* format.
30+
* <p>
31+
* Currently we can only support reading ProGuard files, as Lorenz doesn't have a model
32+
* for all the data represented by the format.
33+
*
34+
* @since 0.5.1
35+
*/
36+
package org.cadixdev.lorenz.io.proguard;

lorenz/src/main/java/org/cadixdev/lorenz/MappingSet.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.cadixdev.bombe.type.ObjectType;
3232
import org.cadixdev.bombe.type.Type;
3333
import org.cadixdev.lorenz.impl.MappingSetImpl;
34+
import org.cadixdev.lorenz.merge.MappingSetMerger;
3435
import org.cadixdev.lorenz.model.ClassMapping;
3536
import org.cadixdev.lorenz.model.TopLevelClassMapping;
3637
import org.cadixdev.lorenz.model.jar.CascadingFieldTypeProvider;
@@ -325,11 +326,7 @@ default MappingSet merge(final MappingSet with) {
325326
* @since 0.5.0
326327
*/
327328
default MappingSet merge(final MappingSet with, final MappingSet parent) {
328-
this.getTopLevelClassMappings().forEach(klass -> {
329-
final TopLevelClassMapping klassWith = with.getOrCreateTopLevelClassMapping(klass.getDeobfuscatedName());
330-
klass.merge(klassWith, parent);
331-
});
332-
return parent;
329+
return MappingSetMerger.create(this, with).merge(parent);
333330
}
334331

335332
/**

0 commit comments

Comments
 (0)