1 /*
  2  * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package jdk.internal.jextract.impl;
 27 
 28 import jdk.incubator.jextract.Declaration;
 29 
 30 import java.io.IOException;
 31 import java.io.UncheckedIOException;
 32 import java.nio.file.Files;
 33 import java.nio.file.Path;
 34 import java.nio.file.StandardOpenOption;
 35 import java.util.Comparator;
 36 import java.util.EnumMap;
 37 import java.util.HashMap;
 38 import java.util.HashSet;
 39 import java.util.List;
 40 import java.util.Map;
 41 import java.util.Set;
 42 import java.util.TreeMap;
 43 import java.util.TreeSet;
 44 import java.util.stream.Collectors;
 45 
 46 public class IncludeHelper {
 47 
 48     public enum IncludeKind {
 49         MACRO,
 50         VAR,
 51         FUNCTION,
 52         TYPEDEF,
 53         STRUCT,
 54         UNION;
 55 
 56         public String optionName() {
 57             return "include-" + name().toLowerCase();
 58         }
 59 
 60         static IncludeKind fromDeclaration(Declaration d) {
 61             if (d instanceof Declaration.Constant) {
 62                 return MACRO;
 63             } else if (d instanceof Declaration.Variable) {
 64                 return VAR;
 65             } else if (d instanceof Declaration.Function) {
 66                 return FUNCTION;
 67             } else if (d instanceof Declaration.Typedef) {
 68                 return TYPEDEF;
 69             } else if (d instanceof Declaration.Scoped scoped) {
 70                 return fromScoped(scoped);
 71             } else {
 72                 throw new IllegalStateException("Cannot get here!");
 73             }
 74         }
 75 
 76         static IncludeKind fromScoped(Declaration.Scoped scoped) {
 77             return switch (scoped.kind()) {
 78                 case STRUCT -> IncludeKind.STRUCT;
 79                 case UNION ->  IncludeKind.UNION;
 80                 default -> throw new IllegalStateException("Cannot get here!");
 81             };
 82         }
 83     }
 84 
 85     private final EnumMap<IncludeKind, Set<String>> includesSymbolNamesByKind = new EnumMap<>(IncludeKind.class);
 86     private final Set<Declaration> usedDeclarations = new HashSet<>();
 87     public String dumpIncludesFile;
 88 
 89     public void addSymbol(IncludeKind kind, String symbolName) {
 90         Set<String> names = includesSymbolNamesByKind.computeIfAbsent(kind, (_unused) -> new HashSet<>());
 91         names.add(symbolName);
 92     }
 93 
 94     public boolean isIncluded(Declaration.Variable variable) {
 95         return checkIncludedAndAddIfNeeded(IncludeKind.VAR, variable);
 96     }
 97 
 98     public boolean isIncluded(Declaration.Function function) {
 99         return checkIncludedAndAddIfNeeded(IncludeKind.FUNCTION, function);
100     }
101 
102     public boolean isIncluded(Declaration.Constant constant) {
103         return checkIncludedAndAddIfNeeded(IncludeKind.MACRO, constant);
104     }
105 
106     public boolean isIncluded(Declaration.Typedef typedef) {
107         return checkIncludedAndAddIfNeeded(IncludeKind.TYPEDEF, typedef);
108     }
109 
110     public boolean isIncluded(Declaration.Scoped scoped) {
111         return checkIncludedAndAddIfNeeded(IncludeKind.fromScoped(scoped), scoped);
112     }
113 
114     private boolean checkIncludedAndAddIfNeeded(IncludeKind kind, Declaration declaration) {
115         boolean included = isIncludedInternal(kind, declaration);
116         if (included && dumpIncludesFile != null) {
117             usedDeclarations.add(declaration);
118         }
119         return included;
120     }
121 
122     private boolean isIncludedInternal(IncludeKind kind, Declaration declaration) {
123         if (!isEnabled()) {
124             return true;
125         } else {
126             Set<String> names = includesSymbolNamesByKind.computeIfAbsent(kind, (_unused) -> new HashSet<>());
127             return names.contains(declaration.name());
128         }
129     }
130 
131     public boolean isEnabled() {
132         return includesSymbolNamesByKind.size() > 0;
133     }
134 
135     public void dumpIncludes() {
136         try (var writer = Files.newBufferedWriter(Path.of(dumpIncludesFile), StandardOpenOption.CREATE)) {
137             Map<Path, Set<Declaration>> declsByPath = usedDeclarations.stream()
138                     .collect(Collectors.groupingBy(d -> d.pos().path(),
139                             () -> new TreeMap<>(Path::compareTo),
140                             Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Declaration::name)))));
141             String lineSep = "";
142             for (Map.Entry<Path, Set<Declaration>> pathEntries : declsByPath.entrySet()) {
143                 writer.append(lineSep);
144                 writer.append("#### Extracted from: " + pathEntries.getKey().toString() + "\n\n");
145                 Map<IncludeKind, List<Declaration>> declsByKind = pathEntries.getValue().stream()
146                         .collect(Collectors.groupingBy(IncludeKind::fromDeclaration));
147                 int maxLengthOptionCol = pathEntries.getValue().stream().mapToInt(d -> d.name().length()).max().getAsInt();
148                 maxLengthOptionCol += 2; // --
149                 maxLengthOptionCol += IncludeKind.FUNCTION.optionName().length(); // max option name
150                 maxLengthOptionCol += 1; // space
151                 for (Map.Entry<IncludeKind, List<Declaration>> kindEntries : declsByKind.entrySet()) {
152                     for (Declaration d : kindEntries.getValue()) {
153                         writer.append(String.format("%-" + maxLengthOptionCol + "s %s",
154                                 "--" + kindEntries.getKey().optionName() + " " + d.name(),
155                                        "# header: " + pathEntries.getKey() + "\n"));
156                     }
157                 }
158                 lineSep = "\n";
159             }
160         } catch (IOException exception) {
161             throw new UncheckedIOException(exception);
162         }
163     }
164 }