View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import com.puppycrawl.tools.checkstyle.api.DetailAST;
8   import com.puppycrawl.tools.checkstyle.api.FullIdent;
9   import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10  import java.util.Collection;
11  import java.util.Set;
12  
13  /**
14   * A method of a JNA binding.
15   *
16   * <p>A type that extends {@code com.sun.jna.Library}, or its Windows
17   * flavour {@code com.sun.jna.win32.StdCallLibrary}, transcribes a native
18   * library function by function. The name of every method and the length of
19   * its parameter list come from the native side, so its author has no say in
20   * either of them and the checks that judge them have nothing to say here.</p>
21   *
22   * <p>Qulice runs Checkstyle on one file at a time, so neither of the two
23   * interfaces is ever resolved to a class. The name in the {@code extends}
24   * or {@code implements} clause is therefore trusted only when it is fully
25   * qualified or when the file imports it from its own package.</p>
26   *
27   * @since 1.0
28   */
29  final class JnaBinding {
30  
31      /**
32       * Canonical names of the JNA interfaces that mark a type as a native
33       * binding.
34       */
35      private static final Collection<String> LIBRARIES = Set.of(
36          "com.sun.jna.Library",
37          "com.sun.jna.win32.StdCallLibrary"
38      );
39  
40      /**
41       * The node of the method declaration.
42       */
43      private final DetailAST node;
44  
45      /**
46       * Ctor.
47       *
48       * @param method The METHOD_DEF node
49       */
50      JnaBinding(final DetailAST method) {
51          this.node = method;
52      }
53  
54      /**
55       * Is this method a part of a JNA binding?
56       *
57       * @return TRUE if the type that declares it maps a native library
58       */
59      boolean is() {
60          DetailAST type = this.node.getParent();
61          boolean answer = false;
62          while (type != null) {
63              if (type.getType() == TokenTypes.CLASS_DEF
64                  || type.getType() == TokenTypes.INTERFACE_DEF) {
65                  answer = JnaBinding.mapped(type);
66                  break;
67              }
68              type = type.getParent();
69          }
70          return answer;
71      }
72  
73      private static boolean mapped(final DetailAST type) {
74          return JnaBinding.declares(type, TokenTypes.EXTENDS_CLAUSE)
75              || JnaBinding.declares(type, TokenTypes.IMPLEMENTS_CLAUSE);
76      }
77  
78      private static boolean declares(final DetailAST type, final int clause) {
79          final DetailAST names = type.findFirstToken(clause);
80          final boolean answer;
81          if (names == null) {
82              answer = false;
83          } else {
84              answer = new ChildStream(names).children()
85                  .anyMatch(JnaBinding::library);
86          }
87          return answer;
88      }
89  
90      private static boolean library(final DetailAST name) {
91          final String text = FullIdent.createFullIdent(name).getText();
92          return JnaBinding.LIBRARIES.stream().anyMatch(
93              known -> known.equals(text)
94                  || JnaBinding.simple(known).equals(text)
95                  && JnaBinding.imported(name, known)
96          );
97      }
98  
99      private static boolean imported(final DetailAST node, final String known) {
100         DetailAST root = node;
101         while (root.getParent() != null) {
102             root = root.getParent();
103         }
104         return new ChildStream(root).children().anyMatch(
105             child -> JnaBinding.importing(child, known)
106         );
107     }
108 
109     private static boolean importing(final DetailAST node, final String known) {
110         return node.getType() == TokenTypes.IMPORT
111             && JnaBinding.brings(
112                 FullIdent.createFullIdentBelow(node).getText(), known
113             );
114     }
115 
116     private static boolean brings(final String text, final String known) {
117         return known.equals(text)
118             || String.format("%s.*", JnaBinding.pack(known)).equals(text);
119     }
120 
121     private static String simple(final String known) {
122         return known.substring(known.lastIndexOf('.') + 1);
123     }
124 
125     private static String pack(final String known) {
126         return known.substring(0, known.lastIndexOf('.'));
127     }
128 }