From 57edfc485c97a39ec121156b9d1b4cf0db0628fb Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Thu, 9 Nov 2023 01:36:41 +0530 Subject: [PATCH] refactor: organize and format code in build-logic/ide --- .../plugins/LexerGeneratorPlugin.kt | 5 +- .../plugins/PropertiesParserPlugin.kt | 11 +- .../plugins/properties/PropertiesParser.java | 134 +-- .../properties/gen/ClassGenerator.java | 831 +++++++++--------- .../plugins/properties/parser/Message.java | 117 +-- .../properties/parser/MessageFile.java | 51 +- .../properties/parser/MessageInfo.java | 160 ++-- .../properties/parser/MessageLine.java | 78 +- .../properties/parser/MessageType.java | 400 +++++---- 9 files changed, 918 insertions(+), 869 deletions(-) diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/LexerGeneratorPlugin.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/LexerGeneratorPlugin.kt index 037223cb0..83b4fafc5 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/LexerGeneratorPlugin.kt +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/LexerGeneratorPlugin.kt @@ -18,10 +18,10 @@ package com.itsaky.androidide.plugins import BuildConfig -import java.io.File import org.antlr.v4.Tool import org.gradle.api.Plugin import org.gradle.api.Project +import java.io.File /** * Generates lexers from the grammar files in 'src/main/antlr' directory of the project. @@ -31,6 +31,7 @@ import org.gradle.api.Project class LexerGeneratorPlugin : Plugin { companion object { + const val LEXER_BASE_PACKAGE = "${BuildConfig.packageName}.lexers" const val EXT_G4 = "g4" } @@ -80,7 +81,7 @@ class LexerGeneratorPlugin : Plugin { "-Xexact-output-dir" ) options.addAll(files) - + Tool(options.toTypedArray()).processGrammarsOnCommandLine() } } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/PropertiesParserPlugin.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/PropertiesParserPlugin.kt index 6ed55d9e1..cc6f15c25 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/PropertiesParserPlugin.kt +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/PropertiesParserPlugin.kt @@ -19,13 +19,13 @@ package com.itsaky.androidide.plugins import com.itsaky.androidide.plugins.properties.PropertiesParser import com.itsaky.androidide.plugins.properties.gen.ClassGenerator -import java.io.File import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.tasks.Delete import org.gradle.api.tasks.compile.JavaCompile +import java.io.File /** * Translates a .properties file into a .java file containing an enum-like Java class which defines @@ -86,10 +86,11 @@ private fun Project.createParserOptions(): List { } val destPath = - it.path.substringAfter("src/main/resources".replace('/', File.separatorChar)).substringBeforeLast(File.separatorChar) + - File.separator + - ClassGenerator.toplevelName(it) + - ".java" + it.path.substringAfter("src/main/resources".replace('/', File.separatorChar)) + .substringBeforeLast(File.separatorChar) + + File.separator + + ClassGenerator.toplevelName(it) + + ".java" val destFile = File(propsDir, destPath) if (destFile.exists() && destFile.lastModified() >= it.lastModified()) { diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/PropertiesParser.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/PropertiesParser.java index d52e583e2..6a4aef5f1 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/PropertiesParser.java +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/PropertiesParser.java @@ -25,94 +25,96 @@ package com.itsaky.androidide.plugins.properties; -import com.itsaky.androidide.plugins.properties.parser.MessageFile; import com.itsaky.androidide.plugins.properties.gen.ClassGenerator; - +import com.itsaky.androidide.plugins.properties.parser.MessageFile; import java.io.File; import java.io.PrintStream; -import java.util.TreeMap; import java.util.Map; +import java.util.TreeMap; -/** Translates a .properties file into a .java file containing an enum-like Java class - * which defines static factory methods for all resource keys in a given resource file.

- * - * Usage: java PropertiesParser -compile [path to .properties file] [output folder where .java file will be written] +/** + * Translates a .properties file into a .java file containing an enum-like Java class which defines + * static factory methods for all resource keys in a given resource file.

+ *

+ * Usage: java PropertiesParser -compile [path to .properties file] [output folder where .java file + * will be written] * * @author mcimadamore */ public class PropertiesParser { - public Logger logger; + public Logger logger; - public PropertiesParser(Logger logger) { - this.logger = logger; - } + public PropertiesParser(Logger logger) { + this.logger = logger; + } - public static void main(String[] args) { - boolean ok = run(args, System.out); - if ( !ok ) { - System.exit(1); - } + public static void main(String[] args) { + boolean ok = run(args, System.out); + if (!ok) { + System.exit(1); } + } - public static boolean run(String[] args, PrintStream out) { - PropertiesParser pp = new PropertiesParser(out::println); - return pp.run(args); - } + public static boolean run(String[] args, PrintStream out) { + PropertiesParser pp = new PropertiesParser(out::println); + return pp.run(args); + } - public static interface Logger { - void info(String msg); - } + public void info(String msg) { + logger.info(msg); + } - public void info(String msg) { - logger.info(msg); + public boolean run(String[] args) { + Map optionsMap = parseOptions(args); + if (optionsMap.isEmpty()) { + usage(); + return false; } + try { + optionsMap.forEach(this::compilePropertyFile); + return true; + } catch (RuntimeException ex) { + ex.printStackTrace(); + return false; + } + } - public boolean run(String[] args) { - Map optionsMap = parseOptions(args); - if (optionsMap.isEmpty()) { - usage(); - return false; - } - try { - optionsMap.forEach(this::compilePropertyFile); - return true; - } catch (RuntimeException ex) { - ex.printStackTrace(); - return false; - } + private void compilePropertyFile(String propertyPath, String outPath) { + try { + File propertyFile = new File(propertyPath); + String prefix = propertyFile.getName().split("\\.")[0]; + MessageFile messageFile = new MessageFile(propertyFile, prefix); + new ClassGenerator().generateFactory(messageFile, new File(outPath)); + } catch (Throwable ex) { + throw new RuntimeException(ex); } + } - private void compilePropertyFile(String propertyPath, String outPath) { - try { - File propertyFile = new File(propertyPath); - String prefix = propertyFile.getName().split("\\.")[0]; - MessageFile messageFile = new MessageFile(propertyFile, prefix); - new ClassGenerator().generateFactory(messageFile, new File(outPath)); - } catch (Throwable ex) { - throw new RuntimeException(ex); - } + private Map parseOptions(String[] args) { + /* Use TreeMap to guarantee stable forEach iteration */ + Map optionsMap = new TreeMap<>(); + for (int i = 0; i < args.length; i++) { + if ("-compile".equals(args[i]) && i + 2 < args.length) { + optionsMap.put(args[++i], args[++i]); + } else { + return new TreeMap<>(); + } } + return optionsMap; + } - private Map parseOptions(String args[]) { - /* Use TreeMap to guarantee stable forEach iteration */ - Map optionsMap = new TreeMap<>(); - for ( int i = 0; i < args.length ; i++ ) { - if ( "-compile".equals(args[i]) && i+2 < args.length ) { - optionsMap.put(args[++i], args[++i]); - } else { - return new TreeMap<>(); - } - } - return optionsMap; - } + private void usage() { + info("usage:"); + info(" java PropertiesParser {-compile path_to_properties_file path_to_java_output_dir}"); + info(""); + info("Example:"); + info(" java PropertiesParser -compile resources/test.properties resources"); + } - private void usage() { - info("usage:"); - info(" java PropertiesParser {-compile path_to_properties_file path_to_java_output_dir}"); - info(""); - info("Example:"); - info(" java PropertiesParser -compile resources/test.properties resources"); - } + public interface Logger { + + void info(String msg); + } } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/gen/ClassGenerator.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/gen/ClassGenerator.java index 904958f9e..52ac948f2 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/gen/ClassGenerator.java +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/gen/ClassGenerator.java @@ -25,11 +25,8 @@ package com.itsaky.androidide.plugins.properties.gen; -import java.io.BufferedReader; - import static java.util.stream.Collectors.toList; -import com.itsaky.androidide.plugins.properties.PropertiesParser; import com.itsaky.androidide.plugins.properties.parser.Message; import com.itsaky.androidide.plugins.properties.parser.MessageFile; import com.itsaky.androidide.plugins.properties.parser.MessageInfo; @@ -40,15 +37,10 @@ import com.itsaky.androidide.plugins.properties.parser.MessageType.CustomType; import com.itsaky.androidide.plugins.properties.parser.MessageType.SimpleType; import com.itsaky.androidide.plugins.properties.parser.MessageType.UnionType; import com.itsaky.androidide.plugins.properties.parser.MessageType.Visitor; - import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; -import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -64,8 +56,8 @@ import java.util.stream.Stream; public class ClassGenerator { - - private static final String TEMPLATE_PROPS = "#\n" + + + private static final String TEMPLATE_PROPS = "#\n" + "# Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.\n" + "# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n" + "#\n" + @@ -137,429 +129,452 @@ public class ClassGenerator { "suppress.warnings=\\\n" + " @SuppressWarnings(\"rawtypes\")\\n"; - /** Empty string - used to generate indentation padding. */ - private final static String INDENT_STRING = " "; + /** + * Empty string - used to generate indentation padding. + */ + private final static String INDENT_STRING = " "; - /** Default indentation step. */ - private final static int INDENT_WIDTH = 4; + /** + * Default indentation step. + */ + private final static int INDENT_WIDTH = 4; - /** File-backed property file containing basic code stubs. */ - static Properties stubs; + /** + * File-backed property file containing basic code stubs. + */ + static Properties stubs; - static { - //init properties from file - stubs = new Properties(); - try (final ByteArrayInputStream in = new ByteArrayInputStream(TEMPLATE_PROPS.getBytes())) { - stubs.load(in); - } catch (IOException ex) { - throw new AssertionError(ex); - } + static { + //init properties from file + stubs = new Properties(); + try (final ByteArrayInputStream in = new ByteArrayInputStream(TEMPLATE_PROPS.getBytes())) { + stubs.load(in); + } catch (IOException ex) { + throw new AssertionError(ex); + } + } + + //where + Visitor stringVisitor = new Visitor() { + @Override + public String visitCustomType(CustomType t, Void aVoid) { + String customType = t.typeString; + return customType.substring(customType.lastIndexOf('.') + 1); } - /** - * Supported stubs in the property file. - */ - enum StubKind { - TOPLEVEL("toplevel.decl"), - FACTORY_CLASS("nested.decl"), - IMPORT("import.decl"), - FACTORY_METHOD_DECL("factory.decl.method"), - FACTORY_METHOD_ARG("factory.decl.method.arg"), - FACTORY_METHOD_BODY("factory.decl.method.body"), - FACTORY_FIELD("factory.decl.field"), - WILDCARDS_EXTENDS("wildcards.extends"), - SUPPRESS_WARNINGS("suppress.warnings"); + @Override + public String visitSimpleType(SimpleType t, Void aVoid) { + return t.clazz; + } - /** stub key (as it appears in the property file) */ - String key; + @Override + public String visitCompoundType(CompoundType t, Void aVoid) { + return StubKind.WILDCARDS_EXTENDS.format(t.kind.clazz.clazz, + t.elemtype.accept(this, null)); + } - StubKind(String key) { - this.key = key; + @Override + public String visitUnionType(UnionType t, Void aVoid) { + throw new AssertionError("Union types should have been denormalized!"); + } + }; + //where + Visitor> importVisitor = new Visitor>() { + @Override + public Void visitCustomType(CustomType t, Set imports) { + imports.add(t.typeString); + return null; + } + + @Override + public Void visitSimpleType(SimpleType t, Set imports) { + if (t.qualifier != null) { + imports.add(t.qualifier + "." + t.clazz); + } + return null; + } + + @Override + public Void visitCompoundType(CompoundType t, Set imports) { + visitSimpleType(t.kind.clazz, imports); + t.elemtype.accept(this, imports); + return null; + } + + @Override + public Void visitUnionType(UnionType t, Set imports) { + Stream.of(t.choices).forEach(c -> c.accept(this, imports)); + return null; + } + }; + //where + Visitor, Void> normalizeVisitor = new Visitor, Void>() { + @Override + public List visitCustomType(CustomType t, Void aVoid) { + return Collections.singletonList(t); + } + + @Override + public List visitSimpleType(SimpleType t, Void aVoid) { + return Collections.singletonList(t); + } + + @Override + public List visitCompoundType(CompoundType t, Void aVoid) { + return t.elemtype.accept(this, null).stream() + .map(nt -> new CompoundType(t.kind, nt)) + .collect(Collectors.toList()); + } + + @Override + public List visitUnionType(UnionType t, Void aVoid) { + return Stream.of(t.choices) + .flatMap(t2 -> t2.accept(this, null).stream()) + .collect(Collectors.toList()); + } + }; + + /** + * Form the name of the toplevel factory class. + */ + public static String toplevelName(File file) { + return Stream.of(file.getName().split("\\.")) + .map(s -> Character.toUpperCase(s.charAt(0)) + s.substring(1)) + .collect(Collectors.joining("")); + } + + /** + * Main entry-point: generate a Java enum-like set of nested factory classes into given output + * folder. The factories are populated as mandated by the comments in the input resource file. + */ + public void generateFactory(MessageFile messageFile, File outDir) { + Map>> groupedEntries = + messageFile.messages.entrySet().stream() + .collect( + Collectors.groupingBy( + e -> FactoryKind.parseFrom(e.getKey().split("\\.")[1]), + TreeMap::new, + toList())); + //generate nested classes + List nestedDecls = new ArrayList<>(); + Set importedTypes = new TreeSet<>(); + for (Map.Entry>> entry : groupedEntries.entrySet()) { + if (entry.getKey() == FactoryKind.OTHER) { + continue; } + //emit members + String members = entry.getValue().stream() + .flatMap(e -> generateFactoryMethodsAndFields(e.getKey(), e.getValue()).stream()) + .collect(Collectors.joining("\n\n")); + //emit nested class + String factoryDecl = + StubKind.FACTORY_CLASS.format(entry.getKey().factoryClazz, indent(members, 1)); + nestedDecls.add(indent(factoryDecl, 1)); + //add imports + entry.getValue().stream().forEach(e -> + importedTypes.addAll(importedTypes(e.getValue().getMessageInfo().getTypes()))); + } + StringBuilder clazzSb = new StringBuilder(); + clazzSb.append(licenseHeader()); + clazzSb.append("\n"); + clazzSb.append(StubKind.TOPLEVEL.format( + packageName(messageFile.file), + String.join("\n", generateImports(importedTypes)), + toplevelName(messageFile.file), + String.join("\n", nestedDecls))); + try (FileWriter fw = new FileWriter( + new File(outDir, toplevelName(messageFile.file) + ".java"))) { + fw.append(clazzSb); + } catch (Throwable ex) { + throw new AssertionError(ex); + } + } - /** - * Subst a list of arguments into a given stub. + /** + * Indent a string to a given level. + */ + String indent(String s, int level) { + return Stream.of(s.split("\n")) + .map(sub -> INDENT_STRING.substring(0, level * INDENT_WIDTH) + sub) + .collect(Collectors.joining("\n")); + } + + /** + * license header. + */ + String licenseHeader() { + return """ + /* + * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. */ - String format(Object... args) { - return MessageFormat.format((String)stubs.get(key), args); - } + + """; + } + + /** + * Retrieve package part of given file object. + */ + String packageName(File file) { + String path = file.getAbsolutePath(); + int begin = path.indexOf("src/main/resources".replace('/', File.separatorChar)) + + "src/main/resources".length(); + String packagePath = path.substring(begin + 1, path.lastIndexOf(File.separatorChar)); + return packagePath.replace(File.separatorChar, '.'); + } + + /** + * Generate a list of import declarations given a set of imported types. + */ + List generateImports(Set importedTypes) { + List importDecls = new ArrayList<>(); + for (String it : importedTypes) { + importDecls.add(StubKind.IMPORT.format(it)); + } + return importDecls; + } + + /** + * Generate a list of factory methods/fields to be added to a given factory nested class. + */ + List generateFactoryMethodsAndFields(String key, Message msg) { + MessageInfo msgInfo = msg.getMessageInfo(); + List lines = msg.getLines(false); + String javadoc = lines.stream() + .filter(ml -> !ml.isInfo() && !ml.isEmptyOrComment()) + .map(ml -> ml.text) + .collect(Collectors.joining("\n *")); + String[] keyParts = key.split("\\."); + FactoryKind k = FactoryKind.parseFrom(keyParts[1]); + String factoryName = factoryName(key); + if (msgInfo.getTypes().isEmpty()) { + //generate field + String factoryField = StubKind.FACTORY_FIELD.format(k.keyClazz, factoryName, + "\"" + keyParts[0] + "\"", + "\"" + Stream.of(keyParts).skip(2).collect(Collectors.joining(".")) + "\"", + javadoc); + return Collections.singletonList(factoryField); + } else { + //generate method + List factoryMethods = new ArrayList<>(); + for (List msgTypes : normalizeTypes(0, msgInfo.getTypes())) { + List types = generateTypes(msgTypes); + List argNames = argNames(types.size()); + String suppressionString = needsSuppressWarnings(msgTypes) ? + StubKind.SUPPRESS_WARNINGS.format() : ""; + String factoryMethod = StubKind.FACTORY_METHOD_DECL.format(suppressionString, k.keyClazz, + factoryName, argDecls(types, argNames).stream().collect(Collectors.joining(", ")), + indent(StubKind.FACTORY_METHOD_BODY.format(k.keyClazz, + "\"" + keyParts[0] + "\"", + "\"" + Stream.of(keyParts).skip(2).collect(Collectors.joining(".")) + "\"", + argNames.stream().collect(Collectors.joining(", "))), 1), + javadoc); + factoryMethods.add(factoryMethod); + } + return factoryMethods; + } + } + + /** + * Form the name of a factory method/field given a resource key. + */ + String factoryName(String key) { + return Stream.of(key.split("[\\.-]")) + .skip(2) + .map(s -> Character.toUpperCase(s.charAt(0)) + s.substring(1)) + .collect(Collectors.joining("")); + } + + /** + * Generate a formal parameter list given a list of types and names. + */ + List argDecls(List types, List args) { + List argNames = new ArrayList<>(); + for (int i = 0; i < types.size(); i++) { + argNames.add(types.get(i) + " " + args.get(i)); + } + return argNames; + } + + /** + * Generate a list of formal parameter names given a size. + */ + List argNames(int size) { + List argNames = new ArrayList<>(); + for (int i = 0; i < size; i++) { + argNames.add(StubKind.FACTORY_METHOD_ARG.format(i)); + } + return argNames; + } + + /** + * Convert a (normalized) parsed type into a string-based representation of some Java type. + */ + List generateTypes(List msgTypes) { + return msgTypes.stream().map(t -> t.accept(stringVisitor, null)).collect(Collectors.toList()); + } + + /** + * See if any of the parsed types in the given list needs warning suppression. + */ + boolean needsSuppressWarnings(List msgTypes) { + return msgTypes.stream().anyMatch(t -> t.accept(suppressWarningsVisitor, null)); + } + + /** + * Retrieve a list of types that need to be imported, so that the factory body can refer to the + * types in the given list using simple names. + */ + Set importedTypes(List msgTypes) { + Set imports = new TreeSet<>(); + msgTypes.forEach(t -> t.accept(importVisitor, imports)); + return imports; + } //where + Visitor suppressWarningsVisitor = new Visitor() { + @Override + public Boolean visitCustomType(CustomType t, Void aVoid) { + //play safe + return true; + } + + @Override + public Boolean visitSimpleType(SimpleType t, Void aVoid) { + switch (t) { + case LIST: + case SET: + return true; + default: + return false; + } + } + + @Override + public Boolean visitCompoundType(CompoundType t, Void aVoid) { + return t.elemtype.accept(this, null); + } + + @Override + public Boolean visitUnionType(UnionType t, Void aVoid) { + return needsSuppressWarnings(Arrays.asList(t.choices)); + } + }; + + /** + * Normalize parsed types in a comment line. If one or more types in the line contains + * alternatives, this routine generate a list of 'overloaded' normalized signatures. + */ + List> normalizeTypes(int idx, List msgTypes) { + if (msgTypes.size() == idx) { + return Collections.singletonList(Collections.emptyList()); + } + MessageType head = msgTypes.get(idx); + List> buf = new ArrayList<>(); + for (MessageType alternative : head.accept(normalizeVisitor, null)) { + for (List rest : normalizeTypes(idx + 1, msgTypes)) { + List temp = new ArrayList<>(rest); + temp.add(0, alternative); + buf.add(temp); + } + } + return buf; + } + + /** + * Supported stubs in the property file. + */ + enum StubKind { + TOPLEVEL("toplevel.decl"), + FACTORY_CLASS("nested.decl"), + IMPORT("import.decl"), + FACTORY_METHOD_DECL("factory.decl.method"), + FACTORY_METHOD_ARG("factory.decl.method.arg"), + FACTORY_METHOD_BODY("factory.decl.method.body"), + FACTORY_FIELD("factory.decl.field"), + WILDCARDS_EXTENDS("wildcards.extends"), + SUPPRESS_WARNINGS("suppress.warnings"); + + /** + * stub key (as it appears in the property file) + */ + String key; + + StubKind(String key) { + this.key = key; } /** - * Nested factory class kind. There are multiple sub-factories, one for each kind of commonly used - * diagnostics (i.e. error, warnings, note, fragment). An additional category is defined for - * those resource keys whose prefix doesn't match any predefined category. + * Subst a list of arguments into a given stub. */ - enum FactoryKind { - ERR("err", "Error", "Errors"), - WARN("warn", "Warning", "Warnings"), - NOTE("note", "Note", "Notes"), - MISC("misc", "Fragment", "Fragments"), - OTHER(null, null, null); + String format(Object... args) { + return MessageFormat.format((String) stubs.get(key), args); + } + } - /** The prefix for this factory kind (i.e. 'err'). */ - String prefix; + /** + * Nested factory class kind. There are multiple sub-factories, one for each kind of commonly used + * diagnostics (i.e. error, warnings, note, fragment). An additional category is defined for those + * resource keys whose prefix doesn't match any predefined category. + */ + enum FactoryKind { + ERR("err", "Error", "Errors"), + WARN("warn", "Warning", "Warnings"), + NOTE("note", "Note", "Notes"), + MISC("misc", "Fragment", "Fragments"), + OTHER(null, null, null); - /** The type of the factory method/fields in this class. */ - String keyClazz; + /** + * The prefix for this factory kind (i.e. 'err'). + */ + String prefix; - /** The class name to be used for this factory. */ - String factoryClazz; + /** + * The type of the factory method/fields in this class. + */ + String keyClazz; - FactoryKind(String prefix, String keyClazz, String factoryClazz) { - this.prefix = prefix; - this.keyClazz = keyClazz; - this.factoryClazz = factoryClazz; - } + /** + * The class name to be used for this factory. + */ + String factoryClazz; - /** - * Utility method for parsing a factory kind from a resource key prefix. - */ - static FactoryKind parseFrom(String prefix) { - for (FactoryKind k : FactoryKind.values()) { - if (k.prefix == null || k.prefix.equals(prefix)) { - return k; - } - } - return null; - } + FactoryKind(String prefix, String keyClazz, String factoryClazz) { + this.prefix = prefix; + this.keyClazz = keyClazz; + this.factoryClazz = factoryClazz; } /** - * Main entry-point: generate a Java enum-like set of nested factory classes into given output - * folder. The factories are populated as mandated by the comments in the input resource file. + * Utility method for parsing a factory kind from a resource key prefix. */ - public void generateFactory(MessageFile messageFile, File outDir) { - Map>> groupedEntries = - messageFile.messages.entrySet().stream() - .collect( - Collectors.groupingBy( - e -> FactoryKind.parseFrom(e.getKey().split("\\.")[1]), - TreeMap::new, - toList())); - //generate nested classes - List nestedDecls = new ArrayList<>(); - Set importedTypes = new TreeSet<>(); - for (Map.Entry>> entry : groupedEntries.entrySet()) { - if (entry.getKey() == FactoryKind.OTHER) continue; - //emit members - String members = entry.getValue().stream() - .flatMap(e -> generateFactoryMethodsAndFields(e.getKey(), e.getValue()).stream()) - .collect(Collectors.joining("\n\n")); - //emit nested class - String factoryDecl = - StubKind.FACTORY_CLASS.format(entry.getKey().factoryClazz, indent(members, 1)); - nestedDecls.add(indent(factoryDecl, 1)); - //add imports - entry.getValue().stream().forEach(e -> - importedTypes.addAll(importedTypes(e.getValue().getMessageInfo().getTypes()))); - } - StringBuilder clazzSb = new StringBuilder(); - clazzSb.append(licenseHeader()); - clazzSb.append("\n"); - clazzSb.append(StubKind.TOPLEVEL.format( - packageName(messageFile.file), - String.join("\n", generateImports(importedTypes)), - toplevelName(messageFile.file), - String.join("\n", nestedDecls))); - try (FileWriter fw = new FileWriter(new File(outDir, toplevelName(messageFile.file) + ".java"))) { - fw.append(clazzSb); - } catch (Throwable ex) { - throw new AssertionError(ex); + static FactoryKind parseFrom(String prefix) { + for (FactoryKind k : FactoryKind.values()) { + if (k.prefix == null || k.prefix.equals(prefix)) { + return k; } + } + return null; } + } - /** - * Indent a string to a given level. - */ - String indent(String s, int level) { - return Stream.of(s.split("\n")) - .map(sub -> INDENT_STRING.substring(0, level * INDENT_WIDTH) + sub) - .collect(Collectors.joining("\n")); - } - /** - * license header. - */ - String licenseHeader() { - return """ - /* - * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - """; - } - - /** - * Retrieve package part of given file object. - */ - String packageName(File file) { - String path = file.getAbsolutePath(); - int begin = path.indexOf("src/main/resources".replace('/', File.separatorChar)) + "src/main/resources".length(); - String packagePath = path.substring(begin + 1, path.lastIndexOf(File.separatorChar)); - return packagePath.replace(File.separatorChar, '.'); - } - - /** - * Form the name of the toplevel factory class. - */ - public static String toplevelName(File file) { - return Stream.of(file.getName().split("\\.")) - .map(s -> Character.toUpperCase(s.charAt(0)) + s.substring(1)) - .collect(Collectors.joining("")); - } - - /** - * Generate a list of import declarations given a set of imported types. - */ - List generateImports(Set importedTypes) { - List importDecls = new ArrayList<>(); - for (String it : importedTypes) { - importDecls.add(StubKind.IMPORT.format(it)); - } - return importDecls; - } - - /** - * Generate a list of factory methods/fields to be added to a given factory nested class. - */ - List generateFactoryMethodsAndFields(String key, Message msg) { - MessageInfo msgInfo = msg.getMessageInfo(); - List lines = msg.getLines(false); - String javadoc = lines.stream() - .filter(ml -> !ml.isInfo() && !ml.isEmptyOrComment()) - .map(ml -> ml.text) - .collect(Collectors.joining("\n *")); - String[] keyParts = key.split("\\."); - FactoryKind k = FactoryKind.parseFrom(keyParts[1]); - String factoryName = factoryName(key); - if (msgInfo.getTypes().isEmpty()) { - //generate field - String factoryField = StubKind.FACTORY_FIELD.format(k.keyClazz, factoryName, - "\"" + keyParts[0] + "\"", - "\"" + Stream.of(keyParts).skip(2).collect(Collectors.joining(".")) + "\"", - javadoc); - return Collections.singletonList(factoryField); - } else { - //generate method - List factoryMethods = new ArrayList<>(); - for (List msgTypes : normalizeTypes(0, msgInfo.getTypes())) { - List types = generateTypes(msgTypes); - List argNames = argNames(types.size()); - String suppressionString = needsSuppressWarnings(msgTypes) ? - StubKind.SUPPRESS_WARNINGS.format() : ""; - String factoryMethod = StubKind.FACTORY_METHOD_DECL.format(suppressionString, k.keyClazz, - factoryName, argDecls(types, argNames).stream().collect(Collectors.joining(", ")), - indent(StubKind.FACTORY_METHOD_BODY.format(k.keyClazz, - "\"" + keyParts[0] + "\"", - "\"" + Stream.of(keyParts).skip(2).collect(Collectors.joining(".")) + "\"", - argNames.stream().collect(Collectors.joining(", "))), 1), - javadoc); - factoryMethods.add(factoryMethod); - } - return factoryMethods; - } - } - - /** - * Form the name of a factory method/field given a resource key. - */ - String factoryName(String key) { - return Stream.of(key.split("[\\.-]")) - .skip(2) - .map(s -> Character.toUpperCase(s.charAt(0)) + s.substring(1)) - .collect(Collectors.joining("")); - } - - /** - * Generate a formal parameter list given a list of types and names. - */ - List argDecls(List types, List args) { - List argNames = new ArrayList<>(); - for (int i = 0 ; i < types.size() ; i++) { - argNames.add(types.get(i) + " " + args.get(i)); - } - return argNames; - } - - /** - * Generate a list of formal parameter names given a size. - */ - List argNames(int size) { - List argNames = new ArrayList<>(); - for (int i = 0 ; i < size ; i++) { - argNames.add(StubKind.FACTORY_METHOD_ARG.format(i)); - } - return argNames; - } - - /** - * Convert a (normalized) parsed type into a string-based representation of some Java type. - */ - List generateTypes(List msgTypes) { - return msgTypes.stream().map(t -> t.accept(stringVisitor, null)).collect(Collectors.toList()); - } - //where - Visitor stringVisitor = new Visitor() { - @Override - public String visitCustomType(CustomType t, Void aVoid) { - String customType = t.typeString; - return customType.substring(customType.lastIndexOf('.') + 1); - } - - @Override - public String visitSimpleType(SimpleType t, Void aVoid) { - return t.clazz; - } - - @Override - public String visitCompoundType(CompoundType t, Void aVoid) { - return StubKind.WILDCARDS_EXTENDS.format(t.kind.clazz.clazz, - t.elemtype.accept(this, null)); - } - - @Override - public String visitUnionType(UnionType t, Void aVoid) { - throw new AssertionError("Union types should have been denormalized!"); - } - }; - - /** - * See if any of the parsed types in the given list needs warning suppression. - */ - boolean needsSuppressWarnings(List msgTypes) { - return msgTypes.stream().anyMatch(t -> t.accept(suppressWarningsVisitor, null)); - } - //where - Visitor suppressWarningsVisitor = new Visitor() { - @Override - public Boolean visitCustomType(CustomType t, Void aVoid) { - //play safe - return true; - } - @Override - public Boolean visitSimpleType(SimpleType t, Void aVoid) { - switch (t) { - case LIST: - case SET: - return true; - default: - return false; - } - } - - @Override - public Boolean visitCompoundType(CompoundType t, Void aVoid) { - return t.elemtype.accept(this, null); - } - - @Override - public Boolean visitUnionType(UnionType t, Void aVoid) { - return needsSuppressWarnings(Arrays.asList(t.choices)); - } - }; - - /** - * Retrieve a list of types that need to be imported, so that the factory body can refer - * to the types in the given list using simple names. - */ - Set importedTypes(List msgTypes) { - Set imports = new TreeSet<>(); - msgTypes.forEach(t -> t.accept(importVisitor, imports)); - return imports; - } - //where - Visitor> importVisitor = new Visitor>() { - @Override - public Void visitCustomType(CustomType t, Set imports) { - imports.add(t.typeString); - return null; - } - - @Override - public Void visitSimpleType(SimpleType t, Set imports) { - if (t.qualifier != null) { - imports.add(t.qualifier + "." + t.clazz); - } - return null; - } - - @Override - public Void visitCompoundType(CompoundType t, Set imports) { - visitSimpleType(t.kind.clazz, imports); - t.elemtype.accept(this, imports); - return null; - } - - @Override - public Void visitUnionType(UnionType t, Set imports) { - Stream.of(t.choices).forEach(c -> c.accept(this, imports)); - return null; - } - }; - - /** - * Normalize parsed types in a comment line. If one or more types in the line contains alternatives, - * this routine generate a list of 'overloaded' normalized signatures. - */ - List> normalizeTypes(int idx, List msgTypes) { - if (msgTypes.size() == idx) return Collections.singletonList(Collections.emptyList()); - MessageType head = msgTypes.get(idx); - List> buf = new ArrayList<>(); - for (MessageType alternative : head.accept(normalizeVisitor, null)) { - for (List rest : normalizeTypes(idx + 1, msgTypes)) { - List temp = new ArrayList<>(rest); - temp.add(0, alternative); - buf.add(temp); - } - } - return buf; - } - //where - Visitor, Void> normalizeVisitor = new Visitor, Void>() { - @Override - public List visitCustomType(CustomType t, Void aVoid) { - return Collections.singletonList(t); - } - - @Override - public List visitSimpleType(SimpleType t, Void aVoid) { - return Collections.singletonList(t); - } - - @Override - public List visitCompoundType(CompoundType t, Void aVoid) { - return t.elemtype.accept(this, null).stream() - .map(nt -> new CompoundType(t.kind, nt)) - .collect(Collectors.toList()); - } - - @Override - public List visitUnionType(UnionType t, Void aVoid) { - return Stream.of(t.choices) - .flatMap(t2 -> t2.accept(this, null).stream()) - .collect(Collectors.toList()); - } - }; } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/Message.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/Message.java index c2f8bc05d..04cb3e046 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/Message.java +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/Message.java @@ -29,66 +29,73 @@ import java.util.ArrayList; import java.util.List; /** - * A message within the message file. - * A message is a series of lines containing a "name=value" property, - * optionally preceded by a comment describing the use of placeholders - * such as {0}, {1}, etc within the property value. + * A message within the message file. A message is a series of lines containing a "name=value" + * property, optionally preceded by a comment describing the use of placeholders such as {0}, {1}, + * etc within the property value. */ public final class Message { - final MessageLine firstLine; - private MessageInfo messageInfo; - Message(MessageLine l) { - firstLine = l; + final MessageLine firstLine; + private MessageInfo messageInfo; + + Message(MessageLine l) { + firstLine = l; + } + + /** + * Get the Info object for this message. It may be empty if there if no comment preceding the + * property specification. + */ + public MessageInfo getMessageInfo() { + if (messageInfo == null) { + MessageLine l = firstLine.prev; + if (l != null && l.isInfo()) { + messageInfo = new MessageInfo(l.text); + } else { + messageInfo = MessageInfo.dummyInfo; + } } + return messageInfo; + } - /** - * Get the Info object for this message. It may be empty if there - * if no comment preceding the property specification. - */ - public MessageInfo getMessageInfo() { - if (messageInfo == null) { - MessageLine l = firstLine.prev; - if (l != null && l.isInfo()) - messageInfo = new MessageInfo(l.text); - else - messageInfo = MessageInfo.dummyInfo; - } - return messageInfo; - } - - /** - * Get all the lines pertaining to this message. - */ - public List getLines(boolean includeAllPrecedingComments) { - List lines = new ArrayList<>(); - MessageLine l = firstLine; - if (includeAllPrecedingComments) { - // scan back to find end of prev message - while (l.prev != null && l.prev.isEmptyOrComment()) - l = l.prev; - // skip leading blank lines - while (l.text.isEmpty()) - l = l.next; - } else { - if (l.prev != null && l.prev.isInfo()) - l = l.prev; - } - - // include any preceding lines - for ( ; l != firstLine; l = l.next) - lines.add(l); - - // include message lines - for (l = firstLine; l != null && l.hasContinuation(); l = l.next) - lines.add(l); - lines.add(l); - - // include trailing blank line if present + /** + * Get all the lines pertaining to this message. + */ + public List getLines(boolean includeAllPrecedingComments) { + List lines = new ArrayList<>(); + MessageLine l = firstLine; + if (includeAllPrecedingComments) { + // scan back to find end of prev message + while (l.prev != null && l.prev.isEmptyOrComment()) { + l = l.prev; + } + // skip leading blank lines + while (l.text.isEmpty()) { l = l.next; - if (l != null && l.text.isEmpty()) - lines.add(l); - - return lines; + } + } else { + if (l.prev != null && l.prev.isInfo()) { + l = l.prev; + } } + + // include any preceding lines + for (; l != firstLine; l = l.next) { + lines.add(l); + } + + // include message lines + for (l = firstLine; l != null && l.hasContinuation(); l = l.next) { + lines.add(l); + } + lines.add(l); + + // include trailing blank line if present + l = l.next; + if (l != null && l.text.isEmpty()) { + lines.add(l); + } + + return lines; + } } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageFile.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageFile.java index 89ce10832..eb4a259b2 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageFile.java +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageFile.java @@ -25,39 +25,42 @@ package com.itsaky.androidide.plugins.properties.parser; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.*; +import java.util.Map; +import java.util.TreeMap; /** * Class to facilitate manipulating compiler.properties. */ public class MessageFile { - MessageLine firstLine; - public Map messages = new TreeMap<>(); - public File file; - public String keyPrefix; + public Map messages = new TreeMap<>(); + public File file; + public String keyPrefix; + MessageLine firstLine; - public MessageFile(File file, String keyPrefix) throws IOException { - this.file = file; - this.keyPrefix = keyPrefix; - read(file); - } + public MessageFile(File file, String keyPrefix) throws IOException { + this.file = file; + this.keyPrefix = keyPrefix; + read(file); + } - final void read(File in) throws IOException { - MessageLine currLine = null; - for (String line : Files.readAllLines(in.toPath())) { - if (currLine == null) - firstLine = currLine = new MessageLine(line); - else - currLine = currLine.append(line); - if (line.startsWith(keyPrefix + ".")) { - int eq = line.indexOf("="); - if (eq > 0) - messages.put(line.substring(0, eq).trim(), new Message(currLine)); - } + final void read(File in) throws IOException { + MessageLine currLine = null; + for (String line : Files.readAllLines(in.toPath())) { + if (currLine == null) { + firstLine = currLine = new MessageLine(line); + } else { + currLine = currLine.append(line); } + if (line.startsWith(keyPrefix + ".")) { + int eq = line.indexOf("="); + if (eq > 0) { + messages.put(line.substring(0, eq).trim(), new Message(currLine)); + } + } } + } } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageInfo.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageInfo.java index 008e60b0e..7e5de8d20 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageInfo.java +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageInfo.java @@ -29,100 +29,102 @@ import com.itsaky.androidide.plugins.properties.parser.MessageType.CompoundType; import com.itsaky.androidide.plugins.properties.parser.MessageType.OrType; import com.itsaky.androidide.plugins.properties.parser.MessageType.SimpleType; import com.itsaky.androidide.plugins.properties.parser.MessageType.UnionType; - import java.util.ArrayList; import java.util.List; /** - * An object to represent the comment that may precede the property - * specification in a Message. - * The comment is modelled as a list of fields, where the fields correspond - * to the placeholder values (e.g. {0}, {1}, etc) within the message value. + * An object to represent the comment that may precede the property specification in a Message. The + * comment is modelled as a list of fields, where the fields correspond to the placeholder values + * (e.g. {0}, {1}, etc) within the message value. */ public final class MessageInfo { - /** The fields of the Info object. */ - List types = new ArrayList<>(); + /** + * Dummy message info to be used when no resource key comment is available. + */ + static final MessageInfo dummyInfo = new MessageInfo(null); + /** + * The fields of the Info object. + */ + List types = new ArrayList<>(); - MessageInfo(String text) throws IllegalArgumentException { - if (text != null) { - if (!text.startsWith("# ")) - throw new IllegalArgumentException(); - String[] segs = text.substring(2).split(", "); - types = new ArrayList<>(); - for (String seg : segs) { - types.add(parseType(seg)); - } + MessageInfo(String text) throws IllegalArgumentException { + if (text != null) { + if (!text.startsWith("# ")) { + throw new IllegalArgumentException(); } + String[] segs = text.substring(2).split(", "); + types = new ArrayList<>(); + for (String seg : segs) { + types.add(parseType(seg)); + } } + } - public List getTypes() { - return types; + public List getTypes() { + return types; + } + + boolean isEmpty() { + return types.isEmpty(); + } + + @Override + public String toString() { + return types.toString(); + } + + /** + * Split the type comment into multiple alternatives (separated by 'or') - then parse each of them + * individually and form an 'or' type. + */ + MessageType parseType(String text) { + int commentStart = text.indexOf("("); + if (commentStart != -1) { + //remove optional comment + text = text.substring(0, commentStart); } - - boolean isEmpty() { - return types.isEmpty(); + text = text.substring(text.indexOf(": ") + 2); + String[] alternatives = text.split(" " + OrType.OR_NAME + " "); + MessageType[] types = new MessageType[alternatives.length]; + for (int i = 0; i < alternatives.length; i++) { + types[i] = parseAlternative(alternatives[i].trim()); } + return types.length > 1 ? + new OrType(types) : types[0]; + } - @Override - public String toString() { - return types.toString(); + /** + * Parse a subset of the type comment; valid matches are simple types, compound types, union types + * and custom types. + */ + MessageType parseAlternative(String text) { + //try with custom types + if (text.charAt(0) == '\'') { + int end = text.indexOf('\'', 1); + return new MessageType.CustomType(text.substring(1, end)); } - - /** - * Split the type comment into multiple alternatives (separated by 'or') - then parse each of them - * individually and form an 'or' type. - */ - MessageType parseType(String text) { - int commentStart = text.indexOf("("); - if (commentStart != -1) { - //remove optional comment - text = text.substring(0, commentStart); - } - text = text.substring(text.indexOf(": ") + 2); - String[] alternatives = text.split(" " + OrType.OR_NAME + " "); - MessageType[] types = new MessageType[alternatives.length]; - for (int i = 0 ; i < alternatives.length ; i++) { - types[i] = parseAlternative(alternatives[i].trim()); - } - return types.length > 1 ? - new OrType(types) : types[0]; + //try with simple types + for (SimpleType st : SimpleType.values()) { + if (text.equals(st.kindName())) { + return st; + } } - - /** - * Parse a subset of the type comment; valid matches are simple types, compound types, - * union types and custom types. - */ - MessageType parseAlternative(String text) { - //try with custom types - if (text.charAt(0) == '\'') { - int end = text.indexOf('\'', 1); - return new MessageType.CustomType(text.substring(1, end)); - } - //try with simple types - for (SimpleType st : SimpleType.values()) { - if (text.equals(st.kindName())) { - return st; - } - } - //try with compound types - for (CompoundType.Kind ck : CompoundType.Kind.values()) { - if (text.startsWith(ck.kindName)) { - MessageType elemtype = parseAlternative(text.substring(ck.kindName.length() + 1).trim()); - return new CompoundType(ck, elemtype); - } - } - //try with union types - for (UnionType.Kind uk : UnionType.Kind.values()) { - if (text.startsWith(uk.kindName)) { - return new UnionType(uk); - } - } - //no match - report a warning - System.err.println("WARNING - unrecognized type: " + text); - return SimpleType.UNKNOWN; + //try with compound types + for (CompoundType.Kind ck : CompoundType.Kind.values()) { + if (text.startsWith(ck.kindName)) { + MessageType elemtype = parseAlternative(text.substring(ck.kindName.length() + 1).trim()); + return new CompoundType(ck, elemtype); + } } - - /** Dummy message info to be used when no resource key comment is available. */ - static final MessageInfo dummyInfo = new MessageInfo(null); + //try with union types + for (UnionType.Kind uk : UnionType.Kind.values()) { + if (text.startsWith(uk.kindName)) { + return new UnionType(uk); + } + } + //no match - report a warning + System.err.println("WARNING - unrecognized type: " + text); + return SimpleType.UNKNOWN; + } } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageLine.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageLine.java index cc56fabf7..54b30085c 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageLine.java +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageLine.java @@ -28,49 +28,49 @@ package com.itsaky.androidide.plugins.properties.parser; import java.util.regex.Pattern; /** - * A line of text within the message file. - * The lines form a doubly linked list for simple navigation. + * A line of text within the message file. The lines form a doubly linked list for simple + * navigation. */ public class MessageLine { - static final Pattern emptyOrCommentPattern = Pattern.compile("( *#.*)?"); - static final Pattern typePattern = Pattern.compile("[-\\\\'A-Z\\.a-z ]+( \\([-A-Za-z 0-9]+\\))?"); - static final Pattern infoPattern = Pattern.compile(String.format("# ([0-9]+: %s, )*[0-9]+: %s", - typePattern.pattern(), typePattern.pattern())); + static final Pattern emptyOrCommentPattern = Pattern.compile("( *#.*)?"); + static final Pattern typePattern = Pattern.compile("[-\\\\'A-Z\\.a-z ]+( \\([-A-Za-z 0-9]+\\))?"); + static final Pattern infoPattern = Pattern.compile(String.format("# ([0-9]+: %s, )*[0-9]+: %s", + typePattern.pattern(), typePattern.pattern())); - public String text; - MessageLine prev; - MessageLine next; + public String text; + MessageLine prev; + MessageLine next; - MessageLine(String text) { - this.text = text; - } - - public boolean isEmptyOrComment() { - return emptyOrCommentPattern.matcher(text).matches(); - } - - public boolean isInfo() { - return infoPattern.matcher(text).matches(); - } - - boolean hasContinuation() { - return (next != null) && text.endsWith("\\"); - } - - MessageLine append(String text) { - MessageLine l = new MessageLine(text); - append(l); - return l; - } - - void append(MessageLine l) { - assert l.prev == null && l.next == null; - l.prev = this; - l.next = next; - if (next != null) { - next.prev = l; - } - next = l; + MessageLine(String text) { + this.text = text; + } + + public boolean isEmptyOrComment() { + return emptyOrCommentPattern.matcher(text).matches(); + } + + public boolean isInfo() { + return infoPattern.matcher(text).matches(); + } + + boolean hasContinuation() { + return (next != null) && text.endsWith("\\"); + } + + MessageLine append(String text) { + MessageLine l = new MessageLine(text); + append(l); + return l; + } + + void append(MessageLine l) { + assert l.prev == null && l.next == null; + l.prev = this; + l.next = next; + if (next != null) { + next.prev = l; } + next = l; + } } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageType.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageType.java index 0ab138bf0..34a08e1cd 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageType.java +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageType.java @@ -30,233 +30,251 @@ package com.itsaky.androidide.plugins.properties.parser; */ public interface MessageType { - /** - * Visitor method. - */ - R accept(Visitor v, A a); + /** + * Visitor method. + */ + R accept(Visitor v, A a); + + /** + * The type as mentioned in the resource file. + */ + String kindName(); + + /** + * A predefined type. All common types mentioned in the resource file comments are meant to be + * included here. + */ + enum SimpleType implements MessageType { + + ANNOTATION("annotation", "Compound", "openjdk.tools.javac.code.Attribute"), + BOOLEAN("boolean", "boolean", null), + COLLECTION("collection", "Collection", "java.util"), + FLAG("flag", "Flag", "openjdk.tools.javac.code.Flags"), + FRAGMENT("fragment", "Fragment", null), + DIAGNOSTIC("diagnostic", "JCDiagnostic", "openjdk.tools.javac.util"), + MODIFIER("modifier", "Modifier", "jdkx.lang.model.element"), + FILE("file", "File", "java.io"), + FILE_OBJECT("file object", "JavaFileObject", "jdkx.tools"), + PATH("path", "Path", "java.nio.file"), + NAME("name", "Name", "openjdk.tools.javac.util"), + NUMBER("number", "int", null), + OPTION_NAME("option name", "Option", "openjdk.tools.javac.main"), + PROFILE("profile", "Profile", "openjdk.tools.javac.jvm"), + SOURCE("source", "Source", "openjdk.tools.javac.code"), + SOURCE_VERSION("source version", "SourceVersion", "jdkx.lang.model"), + STRING("string", "String", null), + SYMBOL("symbol", "Symbol", "openjdk.tools.javac.code"), + SYMBOL_KIND("symbol kind", "Kind", "openjdk.tools.javac.code.Kinds"), + KIND_NAME("kind name", "KindName", "openjdk.tools.javac.code.Kinds"), + TARGET("target", "Target", "openjdk.tools.javac.jvm"), + TOKEN("token", "TokenKind", "openjdk.tools.javac.parser.Tokens"), + TREE_TAG("tree tag", "Tag", "openjdk.tools.javac.tree.JCTree"), + TYPE("type", "Type", "openjdk.tools.javac.code"), + URL("url", "URL", "java.net"), + SET("set", "Set", "java.util"), + LIST("list", "List", "java.util"), + OBJECT("object", "Object", null), + UNUSED("unused", "Void", null), + UNKNOWN("", "UnknownType", null); /** - * The type as mentioned in the resource file. + * name of the predefined type as mentioned in the resource file. */ - String kindName(); + public final String kindName; /** - * A custom type is a type for which a predefined alternative does not exist. As such, it is an - * handy option when prototyping - but usages of custom types should be avoided in product-quality - * resource file comments. - * - * Example: 'openjdk.tools.javac.code.Flags.Flag' + * string-based representation of the type */ - public static class CustomType implements MessageType { + public final String clazz; - /** The string-based representation of this type. */ - public String typeString; + /** + * type qualifier (might be null) + */ + public final String qualifier; - public CustomType(String typeString) { - this.typeString = typeString; - } + SimpleType(String kindName, String clazz, String qualifier) { + this.kindName = kindName; + this.clazz = clazz; + this.qualifier = qualifier; + } - @Override - public String kindName() { - return typeString; - } + @Override + public String kindName() { + return kindName; + } - @Override - public R accept(Visitor v, A a) { - return v.visitCustomType(this, a); - } + @Override + public R accept(Visitor v, A a) { + return v.visitSimpleType(this, a); + } + } + + /** + * A custom type is a type for which a predefined alternative does not exist. As such, it is an + * handy option when prototyping - but usages of custom types should be avoided in product-quality + * resource file comments. + *

+ * Example: 'openjdk.tools.javac.code.Flags.Flag' + */ + class CustomType implements MessageType { + + /** + * The string-based representation of this type. + */ + public String typeString; + + public CustomType(String typeString) { + this.typeString = typeString; + } + + @Override + public String kindName() { + return typeString; + } + + @Override + public R accept(Visitor v, A a) { + return v.visitCustomType(this, a); + } + } + + /** + * A compound type is a collection of some element type. + *

+ * Example: list of string + */ + class CompoundType implements MessageType { + + /** + * The compound type kind. + */ + public final Kind kind; + /** + * The element type. + */ + public final MessageType elemtype; + + public CompoundType(Kind kind, MessageType elemtype) { + this.kind = kind; + this.elemtype = elemtype; + } + + @Override + public String kindName() { + return kind.kindName; + } + + @Override + public R accept(Visitor v, A a) { + return v.visitCompoundType(this, a); } /** - * A predefined type. All common types mentioned in the resource file comments are meant to - * be included here. + * Compound type kind. */ - public enum SimpleType implements MessageType { + public enum Kind { + COLLECTION("collection of", SimpleType.COLLECTION), + LIST("list of", SimpleType.LIST), + SET("set of", SimpleType.SET); - ANNOTATION("annotation", "Compound", "openjdk.tools.javac.code.Attribute"), - BOOLEAN("boolean", "boolean", null), - COLLECTION("collection", "Collection", "java.util"), - FLAG("flag", "Flag", "openjdk.tools.javac.code.Flags"), - FRAGMENT("fragment", "Fragment", null), - DIAGNOSTIC("diagnostic", "JCDiagnostic", "openjdk.tools.javac.util"), - MODIFIER("modifier", "Modifier", "jdkx.lang.model.element"), - FILE("file", "File", "java.io"), - FILE_OBJECT("file object", "JavaFileObject", "jdkx.tools"), - PATH("path", "Path", "java.nio.file"), - NAME("name", "Name", "openjdk.tools.javac.util"), - NUMBER("number", "int", null), - OPTION_NAME("option name", "Option", "openjdk.tools.javac.main"), - PROFILE("profile", "Profile", "openjdk.tools.javac.jvm"), - SOURCE("source", "Source", "openjdk.tools.javac.code"), - SOURCE_VERSION("source version", "SourceVersion", "jdkx.lang.model"), - STRING("string", "String", null), - SYMBOL("symbol", "Symbol", "openjdk.tools.javac.code"), - SYMBOL_KIND("symbol kind", "Kind", "openjdk.tools.javac.code.Kinds"), - KIND_NAME("kind name", "KindName", "openjdk.tools.javac.code.Kinds"), - TARGET("target", "Target", "openjdk.tools.javac.jvm"), - TOKEN("token", "TokenKind", "openjdk.tools.javac.parser.Tokens"), - TREE_TAG("tree tag", "Tag", "openjdk.tools.javac.tree.JCTree"), - TYPE("type", "Type", "openjdk.tools.javac.code"), - URL("url", "URL", "java.net"), - SET("set", "Set", "java.util"), - LIST("list", "List", "java.util"), - OBJECT("object", "Object", null), - UNUSED("unused", "Void", null), - UNKNOWN("", "UnknownType", null); + public final String kindName; + public final SimpleType clazz; - /** name of the predefined type as mentioned in the resource file. */ - public final String kindName; + Kind(String kindName, SimpleType clazz) { + this.kindName = kindName; + this.clazz = clazz; + } + } + } - /** string-based representation of the type */ - public final String clazz; + /** + * A union type represents an alternative between two (or more) types. It can be useful to define + * the type of an argument which can assume multiple (unrelated) values; union types are only + * meant to be used in cases where the alternative comes up frequently enough in the resource file + * comments - in order to avoid cluttered comments. + *

+ * Example: message segment + */ + class UnionType implements MessageType { - /** type qualifier (might be null) */ - public final String qualifier; + /** + * The union type kind. + */ + public final Kind kind; + /** + * The union type alternatives. + */ + public final MessageType[] choices; - SimpleType(String kindName, String clazz, String qualifier) { - this.kindName = kindName; - this.clazz = clazz; - this.qualifier = qualifier; - } + UnionType(Kind kind) { + this(kind, kind.choices); + } - @Override - public String kindName() { - return kindName; - } + protected UnionType(Kind kind, MessageType[] choices) { + this.choices = choices; + this.kind = kind; + } - @Override - public R accept(Visitor v, A a) { - return v.visitSimpleType(this, a); - } + @Override + public String kindName() { + return kind.kindName; + } + + @Override + public R accept(Visitor v, A a) { + return v.visitUnionType(this, a); } /** - * A compound type is a collection of some element type. - * - * Example: list of string + * Union type kind. */ - public static class CompoundType implements MessageType { + public enum Kind { + MESSAGE_SEGMENT("message segment", SimpleType.DIAGNOSTIC, SimpleType.FRAGMENT), + FILE_NAME("file name", SimpleType.FILE, SimpleType.FILE_OBJECT, SimpleType.PATH); - /** - * Compound type kind. - */ - public enum Kind { - COLLECTION("collection of", SimpleType.COLLECTION), - LIST("list of", SimpleType.LIST), - SET("set of", SimpleType.SET); + final String kindName; + final SimpleType[] choices; - public final String kindName; - public final SimpleType clazz; + Kind(String kindName, SimpleType... choices) { + this.kindName = kindName; + this.choices = choices; + } + } + } - Kind(String kindName, SimpleType clazz) { - this.kindName = kindName; - this.clazz = clazz; - } - } + /** + * A subclass of union type representing 'explicit' alternatives in the resource file comments. + * Note: as the token 'or' is parsed with lowest priority, it is not possible, for instance, to + * form a compound type out of an 'or' type. In such cases a plain union type should be used + * instead. + *

+ * Examples: symbol or type + */ + class OrType extends UnionType { - /** The compound type kind. */ - public final Kind kind; + public static final String OR_NAME = "or"; - /** The element type. */ - public final MessageType elemtype; - - public CompoundType(Kind kind, MessageType elemtype) { - this.kind = kind; - this.elemtype = elemtype; - } - - @Override - public String kindName() { - return kind.kindName; - } - - @Override - public R accept(Visitor v, A a) { - return v.visitCompoundType(this, a); - } + public OrType(MessageType... choices) { + super(null, choices); } - /** - * A union type represents an alternative between two (or more) types. It can be useful to - * define the type of an argument which can assume multiple (unrelated) values; union types - * are only meant to be used in cases where the alternative comes up frequently enough in the - * resource file comments - in order to avoid cluttered comments. - * - * Example: message segment - */ - public static class UnionType implements MessageType { - - /** - * Union type kind. - */ - public enum Kind { - MESSAGE_SEGMENT("message segment", SimpleType.DIAGNOSTIC, SimpleType.FRAGMENT), - FILE_NAME("file name", SimpleType.FILE, SimpleType.FILE_OBJECT, SimpleType.PATH); - - final String kindName; - final SimpleType[] choices; - - Kind(String kindName, SimpleType... choices) { - this.kindName = kindName; - this.choices = choices; - } - } - - /** The union type kind. */ - public final Kind kind; - - /** The union type alternatives. */ - public final MessageType[] choices; - - UnionType(Kind kind) { - this(kind, kind.choices); - } - - protected UnionType(Kind kind, MessageType[] choices) { - this.choices = choices; - this.kind = kind; - } - - @Override - public String kindName() { - return kind.kindName; - } - - @Override - public R accept(Visitor v, A a) { - return v.visitUnionType(this, a); - } + @Override + public String kindName() { + return OR_NAME; } + } - /** - * A subclass of union type representing 'explicit' alternatives in the resource file comments. - * Note: as the token 'or' is parsed with lowest priority, it is not possible, for instance, - * to form a compound type out of an 'or' type. In such cases a plain union type should be used - * instead. - * - * Examples: symbol or type - */ - public static class OrType extends UnionType { + /** + * Visitor class. + */ + abstract class Visitor { - public static final String OR_NAME = "or"; + public abstract R visitCustomType(CustomType t, A a); - @Override - public String kindName() { - return OR_NAME; - } + public abstract R visitSimpleType(SimpleType t, A a); - public OrType(MessageType... choices) { - super(null, choices); - } - } + public abstract R visitCompoundType(CompoundType t, A a); - /** - * Visitor class. - */ - public static abstract class Visitor { - public abstract R visitCustomType(CustomType t, A a); - public abstract R visitSimpleType(SimpleType t, A a); - public abstract R visitCompoundType(CompoundType t, A a); - public abstract R visitUnionType(UnionType t, A a); - } + public abstract R visitUnionType(UnionType t, A a); + } }