diff --git a/ChangeLog b/ChangeLog index c8d62413882aafc5e248b2dcbca11bc32e8af904..9b5654bc25c1e8c136ee28fd3f8150d75d78c57e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,9 @@ * Added fluent interface for initializing the components of an AST node (component setting methods return the AST node itself). See https://bitbucket.org/jastadd/jastadd2/pull-requests/10 + * Added option --optimize-imports which can be enabled to + reduce the number of unused imports in generated code. + See https://bitbucket.org/jastadd/jastadd2/issues/310/optimize-imports 2019-03-18 Jesper Öqvist <jesper.oqvist@cs.lth.se> diff --git a/src/jastadd/ast/JastAddCodeGen.jadd b/src/jastadd/ast/JastAddCodeGen.jadd index 961deabc887fa10e28af9a0bfe314ee8d41cc3ab..b2c19256c389a2167e12e30c4555ea5871b12031 100644 --- a/src/jastadd/ast/JastAddCodeGen.jadd +++ b/src/jastadd/ast/JastAddCodeGen.jadd @@ -29,6 +29,7 @@ import org.jastadd.ast.AST.*; import java.io.*; +import java.nio.charset.StandardCharsets; import java.util.*; @@ -228,17 +229,8 @@ aspect JastAddCodeGen { public void InterfaceDecl.jastAddGen(boolean publicModifier) { File file = grammar().targetJavaFile(name()); try { - PrintStream stream = new PrintStream(new FileOutputStream(file)); - if ( !config().license.isEmpty()) { - stream.println(config().license); - } - - // TODO(joqvist): move to template. - if (!config().packageName().isEmpty()) { - stream.println("package " + config().packageName() + ";\n"); - } - - stream.print(grammar().genImportsList()); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(bos, true, "UTF-8"); stream.println(docComment()); stream.println(typeDeclarationString()); @@ -248,7 +240,22 @@ aspect JastAddCodeGen { stream.println("}"); stream.close(); - } catch (FileNotFoundException f) { + String code = new String(bos.toByteArray(), StandardCharsets.UTF_8); + + PrintStream fstream = new PrintStream(new FileOutputStream(file)); + if ( !config().license.isEmpty()) { + fstream.println(config().license); + } + + // TODO(joqvist): move to template. + if (!config().packageName().isEmpty()) { + fstream.println("package " + config().packageName() + ";\n"); + } + + fstream.println(grammar().genImportsList(code)); + fstream.print(code); + fstream.close(); + } catch (IOException e) { System.err.println("Could not create file " + file.getName() + " in " + file.getParent()); System.exit(1); } @@ -259,24 +266,28 @@ aspect JastAddCodeGen { public void EnumDecl.jastAddGen(boolean publicModifier) { File file = grammar().targetJavaFile(name()); try { - PrintStream stream = new PrintStream(new FileOutputStream(file)); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(bos, true, "UTF-8"); + stream.println(Unparser.unparse(simpleNode)); + stream.close(); + String code = new String(bos.toByteArray(), StandardCharsets.UTF_8); + + PrintStream fstream = new PrintStream(new FileOutputStream(file)); if (!config().license.isEmpty()) { - stream.println(config().license); + fstream.println(config().license); } // TODO(joqvist): move to template. if (!config().packageName().isEmpty()) { - stream.println("package " + config().packageName() + ";\n"); + fstream.println("package " + config().packageName() + ";\n"); } - stream.print(grammar().genImportsList()); - stream.println(docComment()); - - stream.println(Unparser.unparse(simpleNode)); - - stream.close(); - } catch (FileNotFoundException f) { - System.err.println("Could not create file " + file.getName() + " in " + file.getParent()); + fstream.print(grammar().genImportsList(code)); + fstream.println(docComment()); + fstream.println(code); + fstream.close(); + } catch (IOException f) { + System.err.format("Could not create file %s in %s.%n", file.getName(), file.getParent()); System.exit(1); } } @@ -284,18 +295,8 @@ aspect JastAddCodeGen { public void ClassDecl.jastAddGen(boolean publicModifier) { File file = grammar().targetJavaFile(name()); try { - PrintStream stream = new PrintStream(new FileOutputStream(file)); - if ( !config().license.isEmpty()) { - stream.println(config().license); - } - - // TODO(joqvist): move to template. - if (!config().packageName().isEmpty()) { - stream.println("package " + config().packageName() + ";\n"); - } - - stream.print(grammar().genImportsList()); - stream.println(docComment()); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(bos, true, "UTF-8"); stream.println(typeDeclarationString()); StringBuffer buf = new StringBuffer(); for (ClassBodyObject obj : classBodyDecls) { @@ -306,13 +307,27 @@ aspect JastAddCodeGen { buf.append("\n\n"); } stream.println(buf.toString()); - emitAbstractSyns(stream); emitInhDeclarations(stream); - - stream.println("}"); stream.close(); - } catch (FileNotFoundException f) { + String code = new String(bos.toByteArray(), StandardCharsets.UTF_8); + + PrintStream fstream = new PrintStream(new FileOutputStream(file)); + if ( !config().license.isEmpty()) { + fstream.println(config().license); + } + + // TODO(joqvist): move to template. + if (!config().packageName().isEmpty()) { + fstream.println("package " + config().packageName() + ";\n"); + } + + fstream.print(grammar().genImportsList(code)); + fstream.println(docComment()); + fstream.println(code); + fstream.println("}"); + fstream.close(); + } catch (IOException f) { System.err.format("Could not create file %s in %s.%n", file.getName(), file.getParent()); System.exit(1); } @@ -321,24 +336,9 @@ aspect JastAddCodeGen { public void ASTDecl.jastAddGen(boolean publicModifier) { File file = grammar().targetJavaFile(name()); try { - PrintStream stream = new PrintStream(new FileOutputStream(file)); - - // Insert comment notifying that this is a generated file. - stream.format("/* This file was generated with %s */%n", - JastAdd.getLongVersionString()); - - if (!config().license.isEmpty()) { - stream.println(config().license); - } - - // TODO(joqvist): move to template. - if (!config().packageName().isEmpty()) { - stream.format("package %s;%n", config().packageName()); - } - - stream.print(grammar().genImportsList()); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(bos, true, "UTF-8"); - stream.println(docComment()); stream.print("public "); if (hasAbstract()) { stream.print("abstract "); @@ -363,11 +363,30 @@ aspect JastAddCodeGen { } stream.print(jastAddImplementsList()); stream.println(" {"); - jastAddAttributes(stream); - stream.println("}"); stream.close(); - } catch (FileNotFoundException f) { + String code = new String(bos.toByteArray(), StandardCharsets.UTF_8); + + PrintStream fstream = new PrintStream(new FileOutputStream(file)); + + // Insert comment notifying that this is a generated file. + fstream.format("/* This file was generated with %s */%n", + JastAdd.getLongVersionString()); + + if (!config().license.isEmpty()) { + fstream.println(config().license); + } + + // TODO(joqvist): move to template. + if (!config().packageName().isEmpty()) { + fstream.format("package %s;%n", config().packageName()); + } + fstream.print(grammar().genImportsList(code)); + fstream.println(docComment()); + fstream.println(code); + fstream.println("}"); + fstream.close(); + } catch (IOException f) { System.err.format("Could not create file %s in %s%n", file.getName(), file.getParent()); System.exit(1); } diff --git a/src/jastadd/ast/JragCodeGen.jrag b/src/jastadd/ast/JragCodeGen.jrag index fba10787d9277f07220ac276930ac3916a485871..6478b3dc1a0b4b91a15ee3b57cf5ce98aeb6b710 100644 --- a/src/jastadd/ast/JragCodeGen.jrag +++ b/src/jastadd/ast/JragCodeGen.jrag @@ -1,4 +1,4 @@ -/* Copyright (c) 2005-2016, The JastAdd Team +/* Copyright (c) 2005-2019, The JastAdd Team * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -118,31 +118,133 @@ aspect JragCodeGen { throw new Error("unhandled cache mode"); } + public class ImportDecl { + public final String modifier; + public final String name; - public String Grammar.genImportsList() { - Set imports = new LinkedHashSet(); + public ImportDecl(String modifier, String name) { + this.modifier = modifier; + this.name = name; + } + + public ImportDecl(String name) { + this("", name); + } + + @Override public int hashCode() { + return getClass().hashCode() ^ modifier.hashCode() ^ name.hashCode(); + } + + @Override public boolean equals(Object other) { + if (other == null || getClass() != other.getClass()) { + return false; + } + ImportDecl o = (ImportDecl) other; + return o.name.equals(name) && o.modifier.equals(modifier); + } + + @Override public String toString() { + return String.format("import%s %s;", modifier, name); + } + } + + public class MultiImportDecl extends ImportDecl { + public MultiImportDecl(String modifier, String name) { + super(modifier, name); + } + + public MultiImportDecl(String name) { + this("", name); + } + } + + class ImportSet { + public final Map<String, Node> all = new HashMap<String, Node>(); + public final Map<String, Node> unmatched = new HashMap<String, Node>(); + + static class Node { + public final Collection<ImportDecl> decls = new ArrayList<ImportDecl>(); + public boolean matched = false; + } + + public ImportSet(Collection<ImportDecl> imports) { + for (ImportDecl decl : imports) { + if (decl instanceof MultiImportDecl) { + continue; + } + int i = decl.name.lastIndexOf('.'); + String name = decl.name.substring(i+1); + Node node = all.get(name); + if (node == null) { + node = new Node(); + all.put(name, node); + unmatched.put(name, node); + } + node.decls.add(decl); + } + } + } + + public Set<ImportDecl> Grammar.importDecls() { + Set<ImportDecl> imports = new LinkedHashSet<ImportDecl>(); if (config().concurrentEval()) { // Extra import declarations needed for concurrent code generation. - imports.add("import java.util.concurrent.atomic.AtomicInteger;"); - imports.add("import java.util.concurrent.atomic.AtomicReference;"); - imports.add("import java.util.concurrent.Future;"); - imports.add("import java.util.concurrent.Executors;"); - imports.add("import java.util.concurrent.ExecutorService;"); - imports.add("import java.util.concurrent.ExecutionException;"); - imports.add("import java.util.concurrent.Callable;"); - imports.add("import java.util.concurrent.ConcurrentMap;"); - imports.add("import java.util.concurrent.ConcurrentHashMap;"); - imports.add("import java.util.ArrayList;"); - imports.add("import java.util.LinkedList;"); - imports.add("import java.util.Collection;"); + imports.add(new ImportDecl("java.util.concurrent.atomic.AtomicInteger")); + imports.add(new ImportDecl("java.util.concurrent.atomic.AtomicReference")); + imports.add(new ImportDecl("java.util.concurrent.Future")); + imports.add(new ImportDecl("java.util.concurrent.Executors")); + imports.add(new ImportDecl("java.util.concurrent.ExecutorService")); + imports.add(new ImportDecl("java.util.concurrent.ExecutionException")); + imports.add(new ImportDecl("java.util.concurrent.Callable")); + imports.add(new ImportDecl("java.util.concurrent.ConcurrentMap")); + imports.add(new ImportDecl("java.util.concurrent.ConcurrentHashMap")); + imports.add(new ImportDecl("java.util.ArrayList")); + imports.add(new ImportDecl("java.util.LinkedList")); + imports.add(new ImportDecl("java.util.Collection")); } for (org.jastadd.jrag.AST.ASTCompilationUnit u : compilationUnits) { imports.addAll(Unparser.getImports(u)); } + return imports; + } + + public String Grammar.genImportsList(String code) { + Set<ImportDecl> imports = importDecls(); + ImportSet set = new ImportSet(imports); + boolean optimize = config().optimizeImports.value(); + if (optimize) { + for (int i = 0; i < code.length(); ++i) { + if (Character.isJavaIdentifierStart(code.charAt(i))) { + int end = i+1; + while (end < code.length() && Character.isJavaIdentifierPart(code.charAt(end))) { + end += 1; + } + String id = code.substring(i, end); + if (set.unmatched.containsKey(id)) { + set.unmatched.get(id).matched = true; + set.unmatched.remove(id); + if (set.unmatched.isEmpty()) { + break; + } + } + i = end-1; + } + } + } StringBuilder buf = new StringBuilder(); - for (Iterator iter = imports.iterator(); iter.hasNext(); ) { - buf.append(iter.next()); - buf.append('\n'); + for (ImportDecl decl : imports) { + if (decl instanceof MultiImportDecl) { + buf.append(decl); + buf.append('\n'); + } + } + for (ImportSet.Node node : set.all.values()) { + if (!optimize || node.matched) { + for (ImportDecl decl : node.decls) { + buf.append(decl); + buf.append('\n'); + } + } } return buf.toString(); } diff --git a/src/java/org/jastadd/Configuration.java b/src/java/org/jastadd/Configuration.java index bd77c0421ac3f1cbad66978db8689f52e97dcb4b..5e304be489703c740bac1b8b5ba53b67a295bdbe 100644 --- a/src/java/org/jastadd/Configuration.java +++ b/src/java/org/jastadd/Configuration.java @@ -408,6 +408,10 @@ public class Configuration { .templateVariable("EmptyContainerSingletons") .nonStandard(); + public final Option<Boolean> optimizeImports = new FlagOption("optimize-imports", + "remove unused imports in generated code") + .nonStandard(); + /** * Indicates if there were unknown command-line options */ @@ -494,6 +498,9 @@ public class Configuration { allOptions.add(numThreadsOption); allOptions.add(concurrentMap); + // New since 2.3.4 + allOptions.add(optimizeImports); + // Deprecated in 2.1.5. allOptions.add(doxygenOption); allOptions.add(cacheAllOption); diff --git a/src/java/org/jastadd/JastAddTask.java b/src/java/org/jastadd/JastAddTask.java index 172e2d36fd292de6ed84e8b1d01a5e69758f9091..d5344538e8870f7413896ccb0fc51563ef2b5b54 100644 --- a/src/java/org/jastadd/JastAddTask.java +++ b/src/java/org/jastadd/JastAddTask.java @@ -1,4 +1,4 @@ -/* Copyright (c) 2005-2013, The JastAdd Team +/* Copyright (c) 2005-2019, The JastAdd Team * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -298,6 +298,10 @@ public class JastAddTask extends Task { setOption(config.emptyContainerSingletons, enable); } + public void setOptimizeImports(boolean enable) { + setOption(config.optimizeImports, enable); + } + @Override public void execute() throws BuildException { System.err.println("generating node types and weaving aspects"); diff --git a/src/javacc/jrag/Unparser.java b/src/javacc/jrag/Unparser.java index 56af94875a2274749d71daffa956615e9bb0047b..fa1bca6fd93fe668e785cd4d94bd4d34289221bc 100644 --- a/src/javacc/jrag/Unparser.java +++ b/src/javacc/jrag/Unparser.java @@ -4,18 +4,20 @@ import java.util.Set; import java.util.LinkedHashSet; import org.jastadd.jrag.AST.*; +import org.jastadd.ast.AST.ImportDecl; +import org.jastadd.ast.AST.MultiImportDecl; public class Unparser implements JragParserVisitor { - public static Set getImports(ASTCompilationUnit self) { - Set imports = new LinkedHashSet(); + public static Set<ImportDecl> getImports(ASTCompilationUnit self) { + Set<ImportDecl> imports = new LinkedHashSet<ImportDecl>(); for (int i = 0; i < self.jjtGetNumChildren(); i++) { Unparser.getImports((SimpleNode) self.jjtGetChild(i), imports); } return imports; } - public static void getImports(SimpleNode self, Set imports) { + public static void getImports(SimpleNode self, Set<ImportDecl> imports) { if (self instanceof ASTImportDeclaration) { Token t = new Token(); t.next = self.firstToken; @@ -27,7 +29,18 @@ public class Unparser implements JragParserVisitor { } buf.append(Util.addUnicodeEscapes(t.image)); } - imports.add(buf.toString().trim()); + String decl = buf.toString().trim(); + String modifier = ""; + if (decl.startsWith("import static")) { + modifier = " static"; + } + String name = decl.substring(7 + modifier.length()); + name = name.substring(0, name.length()-1); + if (name.endsWith(".*")) { + imports.add(new MultiImportDecl(modifier, name)); + } else { + imports.add(new ImportDecl(modifier, name)); + } } }