aboutsummaryrefslogtreecommitdiffstats
path: root/abi-check-plugin/src/main/java/com/yahoo/abicheck/classtree/ClassFileTree.java
blob: 00141b04215028a44330dc4246caa38f042402fc (plain) (blame)
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
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.abicheck.classtree;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collection;
import java.util.Deque;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public abstract class ClassFileTree implements AutoCloseable {

  public static ClassFileTree fromJar(JarFile jarFile) {
    Map<String, Package> rootPackages = new HashMap<>();

    Enumeration<JarEntry> jarEntries = jarFile.entries();
    while (jarEntries.hasMoreElements()) {
      JarEntry entry = jarEntries.nextElement();
      if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
        Deque<String> parts = new ArrayDeque<>(Arrays.asList(entry.getName().split("/")));
        String className = parts.removeLast();
        Package pkg = rootPackages
            .computeIfAbsent(parts.removeFirst(), name -> new Package(null, name));
        for (String part : parts) {
          pkg = pkg.getOrCreateSubPackage(part);
        }
        pkg.addClass(new ClassFile(pkg, className) {

          @Override
          public InputStream getInputStream() throws IOException {
            return jarFile.getInputStream(entry);
          }
        });
      }
    }

    return new ClassFileTree() {
      @Override
      public Collection<Package> getRootPackages() {
        return rootPackages.values();
      }

      @Override
      public void close() throws IOException {
        jarFile.close();
      }
    };
  }

  public abstract void close() throws IOException;

  public abstract Collection<Package> getRootPackages();

  public static abstract class ClassFile {

    private final Package parent;
    private final String name;

    private ClassFile(Package parent, String name) {
      this.parent = parent;
      this.name = name;
    }

    public abstract InputStream getInputStream() throws IOException;

    public String getName() {
      return name;
    }

    // CLOVER:OFF
    // Testing debug methods is not necessary
    @Override
    public String toString() {
      return "ClassFile(" + parent.getFullyQualifiedName() + "." + name + ")";
    }
    // CLOVER:ON
  }

  public static class Package {

    private final Package parent;
    private final String name;
    private final Map<String, Package> subPackages = new HashMap<>();
    private final Set<ClassFile> classFiles = new HashSet<>();

    private Package(Package parent, String name) {
      this.parent = parent;
      this.name = name;
    }

    private Package getOrCreateSubPackage(String name) {
      return subPackages.computeIfAbsent(name, n -> new Package(this, n));
    }

    private void addClass(ClassFile klazz) {
      classFiles.add(klazz);
    }

    public String getFullyQualifiedName() {
      if (parent == null) {
        return name;
      } else {
        return parent.getFullyQualifiedName() + "." + name;
      }
    }

    public Collection<Package> getSubPackages() {
      return subPackages.values();
    }

    public Collection<ClassFile> getClassFiles() {
      return classFiles;
    }

    // CLOVER:OFF
    // Testing debug methods is not necessary
    @Override
    public String toString() {
      return "Package(" + getFullyQualifiedName() + ")";
    }
    // CLOVER:ON
  }
}