mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
refactor: organize and format code in build-logic/ide
This commit is contained in:
@@ -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<Project> {
|
||||
|
||||
companion object {
|
||||
|
||||
const val LEXER_BASE_PACKAGE = "${BuildConfig.packageName}.lexers"
|
||||
const val EXT_G4 = "g4"
|
||||
}
|
||||
@@ -80,7 +81,7 @@ class LexerGeneratorPlugin : Plugin<Project> {
|
||||
"-Xexact-output-dir"
|
||||
)
|
||||
options.addAll(files)
|
||||
|
||||
|
||||
Tool(options.toTypedArray()).processGrammarsOnCommandLine()
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -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<String> {
|
||||
}
|
||||
|
||||
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()) {
|
||||
|
||||
+68
-66
@@ -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. <P>
|
||||
*
|
||||
* 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. <P>
|
||||
* <p>
|
||||
* 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<String, String> 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<String, String> 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<String, String> parseOptions(String[] args) {
|
||||
/* Use TreeMap to guarantee stable forEach iteration */
|
||||
Map<String, String> 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<String, String> parseOptions(String args[]) {
|
||||
/* Use TreeMap to guarantee stable forEach iteration */
|
||||
Map<String, String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
+423
-408
@@ -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<String, Void> stringVisitor = new Visitor<String, Void>() {
|
||||
@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<Void, Set<String>> importVisitor = new Visitor<Void, Set<String>>() {
|
||||
@Override
|
||||
public Void visitCustomType(CustomType t, Set<String> imports) {
|
||||
imports.add(t.typeString);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitSimpleType(SimpleType t, Set<String> imports) {
|
||||
if (t.qualifier != null) {
|
||||
imports.add(t.qualifier + "." + t.clazz);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitCompoundType(CompoundType t, Set<String> imports) {
|
||||
visitSimpleType(t.kind.clazz, imports);
|
||||
t.elemtype.accept(this, imports);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitUnionType(UnionType t, Set<String> imports) {
|
||||
Stream.of(t.choices).forEach(c -> c.accept(this, imports));
|
||||
return null;
|
||||
}
|
||||
};
|
||||
//where
|
||||
Visitor<List<MessageType>, Void> normalizeVisitor = new Visitor<List<MessageType>, Void>() {
|
||||
@Override
|
||||
public List<MessageType> visitCustomType(CustomType t, Void aVoid) {
|
||||
return Collections.singletonList(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MessageType> visitSimpleType(SimpleType t, Void aVoid) {
|
||||
return Collections.singletonList(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MessageType> 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<MessageType> 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<FactoryKind, List<Map.Entry<String, Message>>> groupedEntries =
|
||||
messageFile.messages.entrySet().stream()
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
e -> FactoryKind.parseFrom(e.getKey().split("\\.")[1]),
|
||||
TreeMap::new,
|
||||
toList()));
|
||||
//generate nested classes
|
||||
List<String> nestedDecls = new ArrayList<>();
|
||||
Set<String> importedTypes = new TreeSet<>();
|
||||
for (Map.Entry<FactoryKind, List<Map.Entry<String, Message>>> 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<String> generateImports(Set<String> importedTypes) {
|
||||
List<String> 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<String> generateFactoryMethodsAndFields(String key, Message msg) {
|
||||
MessageInfo msgInfo = msg.getMessageInfo();
|
||||
List<MessageLine> 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<String> factoryMethods = new ArrayList<>();
|
||||
for (List<MessageType> msgTypes : normalizeTypes(0, msgInfo.getTypes())) {
|
||||
List<String> types = generateTypes(msgTypes);
|
||||
List<String> 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<String> argDecls(List<String> types, List<String> args) {
|
||||
List<String> 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<String> argNames(int size) {
|
||||
List<String> 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<String> generateTypes(List<MessageType> 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<MessageType> 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<String> importedTypes(List<MessageType> msgTypes) {
|
||||
Set<String> imports = new TreeSet<>();
|
||||
msgTypes.forEach(t -> t.accept(importVisitor, imports));
|
||||
return imports;
|
||||
} //where
|
||||
Visitor<Boolean, Void> suppressWarningsVisitor = new Visitor<Boolean, Void>() {
|
||||
@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<List<MessageType>> normalizeTypes(int idx, List<MessageType> msgTypes) {
|
||||
if (msgTypes.size() == idx) {
|
||||
return Collections.singletonList(Collections.emptyList());
|
||||
}
|
||||
MessageType head = msgTypes.get(idx);
|
||||
List<List<MessageType>> buf = new ArrayList<>();
|
||||
for (MessageType alternative : head.accept(normalizeVisitor, null)) {
|
||||
for (List<MessageType> rest : normalizeTypes(idx + 1, msgTypes)) {
|
||||
List<MessageType> 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<FactoryKind, List<Map.Entry<String, Message>>> groupedEntries =
|
||||
messageFile.messages.entrySet().stream()
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
e -> FactoryKind.parseFrom(e.getKey().split("\\.")[1]),
|
||||
TreeMap::new,
|
||||
toList()));
|
||||
//generate nested classes
|
||||
List<String> nestedDecls = new ArrayList<>();
|
||||
Set<String> importedTypes = new TreeSet<>();
|
||||
for (Map.Entry<FactoryKind, List<Map.Entry<String, Message>>> 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<String> generateImports(Set<String> importedTypes) {
|
||||
List<String> 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<String> generateFactoryMethodsAndFields(String key, Message msg) {
|
||||
MessageInfo msgInfo = msg.getMessageInfo();
|
||||
List<MessageLine> 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<String> factoryMethods = new ArrayList<>();
|
||||
for (List<MessageType> msgTypes : normalizeTypes(0, msgInfo.getTypes())) {
|
||||
List<String> types = generateTypes(msgTypes);
|
||||
List<String> 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<String> argDecls(List<String> types, List<String> args) {
|
||||
List<String> 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<String> argNames(int size) {
|
||||
List<String> 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<String> generateTypes(List<MessageType> msgTypes) {
|
||||
return msgTypes.stream().map(t -> t.accept(stringVisitor, null)).collect(Collectors.toList());
|
||||
}
|
||||
//where
|
||||
Visitor<String, Void> stringVisitor = new Visitor<String, Void>() {
|
||||
@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<MessageType> msgTypes) {
|
||||
return msgTypes.stream().anyMatch(t -> t.accept(suppressWarningsVisitor, null));
|
||||
}
|
||||
//where
|
||||
Visitor<Boolean, Void> suppressWarningsVisitor = new Visitor<Boolean, Void>() {
|
||||
@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<String> importedTypes(List<MessageType> msgTypes) {
|
||||
Set<String> imports = new TreeSet<>();
|
||||
msgTypes.forEach(t -> t.accept(importVisitor, imports));
|
||||
return imports;
|
||||
}
|
||||
//where
|
||||
Visitor<Void, Set<String>> importVisitor = new Visitor<Void, Set<String>>() {
|
||||
@Override
|
||||
public Void visitCustomType(CustomType t, Set<String> imports) {
|
||||
imports.add(t.typeString);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitSimpleType(SimpleType t, Set<String> imports) {
|
||||
if (t.qualifier != null) {
|
||||
imports.add(t.qualifier + "." + t.clazz);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitCompoundType(CompoundType t, Set<String> imports) {
|
||||
visitSimpleType(t.kind.clazz, imports);
|
||||
t.elemtype.accept(this, imports);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitUnionType(UnionType t, Set<String> 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<List<MessageType>> normalizeTypes(int idx, List<MessageType> msgTypes) {
|
||||
if (msgTypes.size() == idx) return Collections.singletonList(Collections.emptyList());
|
||||
MessageType head = msgTypes.get(idx);
|
||||
List<List<MessageType>> buf = new ArrayList<>();
|
||||
for (MessageType alternative : head.accept(normalizeVisitor, null)) {
|
||||
for (List<MessageType> rest : normalizeTypes(idx + 1, msgTypes)) {
|
||||
List<MessageType> temp = new ArrayList<>(rest);
|
||||
temp.add(0, alternative);
|
||||
buf.add(temp);
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
//where
|
||||
Visitor<List<MessageType>, Void> normalizeVisitor = new Visitor<List<MessageType>, Void>() {
|
||||
@Override
|
||||
public List<MessageType> visitCustomType(CustomType t, Void aVoid) {
|
||||
return Collections.singletonList(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MessageType> visitSimpleType(SimpleType t, Void aVoid) {
|
||||
return Collections.singletonList(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MessageType> 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<MessageType> visitUnionType(UnionType t, Void aVoid) {
|
||||
return Stream.of(t.choices)
|
||||
.flatMap(t2 -> t2.accept(this, null).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+62
-55
@@ -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<MessageLine> getLines(boolean includeAllPrecedingComments) {
|
||||
List<MessageLine> 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<MessageLine> getLines(boolean includeAllPrecedingComments) {
|
||||
List<MessageLine> 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;
|
||||
}
|
||||
}
|
||||
|
||||
+27
-24
@@ -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<String, Message> messages = new TreeMap<>();
|
||||
public File file;
|
||||
public String keyPrefix;
|
||||
public Map<String, Message> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+81
-79
@@ -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<MessageType> 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<MessageType> 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<MessageType> getTypes() {
|
||||
return types;
|
||||
public List<MessageType> 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;
|
||||
}
|
||||
}
|
||||
|
||||
+39
-39
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+209
-191
@@ -30,233 +30,251 @@ package com.itsaky.androidide.plugins.properties.parser;
|
||||
*/
|
||||
public interface MessageType {
|
||||
|
||||
/**
|
||||
* Visitor method.
|
||||
*/
|
||||
<R, A> R accept(Visitor<R, A> v, A a);
|
||||
/**
|
||||
* Visitor method.
|
||||
*/
|
||||
<R, A> R accept(Visitor<R, A> 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("<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, A> R accept(Visitor<R, A> v, A a) {
|
||||
return v.visitCustomType(this, a);
|
||||
}
|
||||
@Override
|
||||
public <R, A> R accept(Visitor<R, A> 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.
|
||||
* <p>
|
||||
* 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, A> R accept(Visitor<R, A> v, A a) {
|
||||
return v.visitCustomType(this, a);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A compound type is a collection of some element type.
|
||||
* <p>
|
||||
* 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, A> R accept(Visitor<R, A> 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("<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.
|
||||
* <p>
|
||||
* 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, A> R accept(Visitor<R, A> v, A a) {
|
||||
return v.visitSimpleType(this, a);
|
||||
}
|
||||
@Override
|
||||
public String kindName() {
|
||||
return kind.kindName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R, A> R accept(Visitor<R, A> 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.
|
||||
* <p>
|
||||
* 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, A> R accept(Visitor<R, A> 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, A> R accept(Visitor<R, A> 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<R, A> {
|
||||
|
||||
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<R, A> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user