package org.jooq.codegen;

import static org.jooq.codegen.GenerationUtil.PLAIN_GENERIC_TYPE_PATTERN;
import static org.jooq.codegen.GenerationUtil.TYPE_REFERENCE_PATTERN;

import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jooq.meta.jaxb.GeneratedSerialVersionUID;
import org.jooq.tools.StringUtils;

A wrapper for a PrintWriter

This wrapper adds Java printing features to the general GeneratorWriter

Author:Lukas Eder
/** * A wrapper for a {@link PrintWriter} * <p> * This wrapper adds Java printing features to the general * {@link GeneratorWriter} * * @author Lukas Eder */
public class JavaWriter extends GeneratorWriter<JavaWriter> { private static final String SERIAL_STATEMENT = "__SERIAL_STATEMENT__"; private static final String IMPORT_STATEMENT = "__IMPORT_STATEMENT__"; private final Pattern fullyQualifiedTypes; private final boolean javadoc; private final Set<String> refConflicts; private final Set<String> qualifiedTypes = new TreeSet<>(qualifiedTypeComparator()); private final Map<String, String> unqualifiedTypes = new TreeMap<>(); private final String className; private String packageName; private final boolean isJava; private final boolean isScala; private final boolean isKotlin; private final GeneratedSerialVersionUID generatedSerialVersionUID; public JavaWriter(File file, String fullyQualifiedTypes) { this(file, fullyQualifiedTypes, null); } public JavaWriter(File file, String fullyQualifiedTypes, String encoding) { this(file, fullyQualifiedTypes, encoding, true); } public JavaWriter(File file, String fullyQualifiedTypes, String encoding, boolean javadoc) { this(file, fullyQualifiedTypes, encoding, javadoc, null); } public JavaWriter(File file, String fullyQualifiedTypes, String encoding, boolean javadoc, Files files) { this(file, fullyQualifiedTypes, encoding, javadoc, files, GeneratedSerialVersionUID.CONSTANT); } public JavaWriter(File file, String fullyQualifiedTypes, String encoding, boolean javadoc, Files files, GeneratedSerialVersionUID generatedSerialVersionUID) { super(file, encoding, files); this.className = file.getName().replaceAll("\\.(java|scala|kt)$", ""); this.isJava = file.getName().endsWith(".java"); this.isScala = file.getName().endsWith(".scala"); this.isKotlin = file.getName().endsWith(".kt"); this.refConflicts = new HashSet<>(); this.fullyQualifiedTypes = fullyQualifiedTypes == null ? null : Pattern.compile(fullyQualifiedTypes); this.javadoc = javadoc; this.generatedSerialVersionUID = generatedSerialVersionUID; if (isJava || isKotlin) tabString(" "); else if (isScala) tabString(" "); } public JavaWriter print(Class<?> clazz) { printClass(clazz.getCanonicalName()); return this; } public JavaWriter printClass(String clazz) { print(ref(clazz)); return this; } public JavaWriter javadoc(String string, Object... args) { println(); if (javadoc) { // [#3450] [#4575] [#7693] Must not print */ inside Javadoc String escaped = escapeJavadoc(string); Object[] escapedArgs = Arrays.copyOf(args, args.length); for (int i = 0; i < escapedArgs.length; i++) escapedArgs[i] = escapeJavadoc(escapedArgs[i]); println("/**"); println(" * " + escaped, escapedArgs); println(" */"); } return this; } @SuppressWarnings("unchecked") static Object escapeJavadoc(Object object) { if (object instanceof String) { return escapeJavadoc((String) object); } else if (object instanceof List) { List<Object> result = new ArrayList<>(); for (Object o : (List<Object>) object) result.add(escapeJavadoc(o)); return result; } else return object; } static String escapeJavadoc(String string) { // [#3450] [#4880] [#7693] Must not print */ inside Javadoc return string .replace("/*", "/ *") .replace("*/", "* /") .replace("\\u002a/", "\\u002a /") .replace("*\\u002f", "* \\u002f") .replace("\\u002a\\u002f", "\\u002a \\u002f"); } public JavaWriter header(String header, Object... args) { println(); println("// -------------------------------------------------------------------------"); println("// " + header, args); println("// -------------------------------------------------------------------------"); return this; } public JavaWriter override() { println("@%s", Override.class); return this; } public JavaWriter overrideIf(boolean override) { if (override) println("@%s", Override.class); return this; } public JavaWriter overrideInherit() { println(); override(); return this; } public JavaWriter overrideInheritIf(boolean override) { println(); if (override) override(); return this; } public void printSerial() { if (isJava && generatedSerialVersionUID != GeneratedSerialVersionUID.OFF) { println(); println("private static final long serialVersionUID = %s;", SERIAL_STATEMENT); } } @SuppressWarnings("hiding") public void printPackageSpecification(String packageName) { this.packageName = packageName; if (isScala || isKotlin) println("package %s", packageName); else println("package %s;", packageName); } public void printImports() { println(IMPORT_STATEMENT); }
Subclasses may override this to specify their own order of qualified types.
/** * Subclasses may override this to specify their own order of qualified types. */
protected Comparator<String> qualifiedTypeComparator() { return null; } @Override protected String beforeClose(String string) { string = super.beforeClose(string); StringBuilder importString = new StringBuilder(); Pattern samePackagePattern = Pattern.compile(packageName + "\\.[^\\.]+"); String dotClassName = "." + className; String previous = ""; for (String imp : qualifiedTypes) { // [#4021] For Scala interoperability, we better also import // java.lang types if ((isJava || isKotlin) && imp.startsWith("java.lang.")) continue; // [#6248] java.lang.Integer is converted to kotlin.Int, and shouldn't be imported if (isKotlin && imp.startsWith("kotlin.") && !imp.substring("kotlin.".length()).contains(".")) continue; // Don't import the class itself if (imp.endsWith(dotClassName)) continue; // [#4229] [#4531] [#11103] Avoid warnings due to unnecessary same-package imports if (packageName != null && packageName.length() > 0 && samePackagePattern.matcher(imp).matches()) continue; String topLevelPackage = imp.split("\\.")[0]; if (!topLevelPackage.equals(previous)) importString.append(newlineString()); importString.append("import ") .append(imp) .append(isScala || isKotlin ? "" : ";").append(newlineString()); previous = topLevelPackage; } string = string.replaceAll(IMPORT_STATEMENT, Matcher.quoteReplacement(importString.toString())); if (isJava) { switch (StringUtils.defaultIfNull(generatedSerialVersionUID, GeneratedSerialVersionUID.CONSTANT)) { case HASH: string = string.replaceAll(SERIAL_STATEMENT, Matcher.quoteReplacement(String.valueOf(string.hashCode()))); break; case OFF: break; case CONSTANT: default: string = string.replaceAll(SERIAL_STATEMENT, Matcher.quoteReplacement("1L")); break; } } return string; } public JavaWriter refConflicts(List<String> conflicts) { this.refConflicts.addAll(conflicts); return this; } @Override protected List<String> ref(List<String> clazz, int keepSegments) { List<String> result = new ArrayList<>(clazz == null ? 0 : clazz.size()); if (clazz != null) { for (String c : clazz) { // Skip unqualified and primitive types checks: { if (!c.contains(".")) break checks; c = patchKotlinClasses(c); // com.example.Table.TABLE.COLUMN (with keepSegments = 3) if (fullyQualifiedTypes != null && fullyQualifiedTypes.matcher(c).matches()) break checks; Matcher m = TYPE_REFERENCE_PATTERN.matcher(c); if (!m.find()) break checks; // [com, example, Table, TABLE, COLUMN] List<String> split = Arrays.asList(m.group(1).split("\\.")); // com.example.Table String qualifiedType = StringUtils.join(split.subList(0, split.size() - keepSegments + 1).toArray(), "."); // Table String unqualifiedType = split.get(split.size() - keepSegments); // Table.TABLE.COLUMN String remainder = StringUtils.join(split.subList(split.size() - keepSegments, split.size()).toArray(), "."); // [#9697] Don't import a class from a different package by the same name as this class if ((className.equals(unqualifiedType) && (packageName == null || !qualifiedType.equals(packageName + "." + className))) || (unqualifiedTypes.containsKey(unqualifiedType) && !qualifiedType.equals(unqualifiedTypes.get(unqualifiedType)))) break checks; // [#10561] Don't import type that conflicts with a local identifier if (refConflicts.contains(unqualifiedType)) break checks; // [#10561] Don't import a class that conflicts with a local identifier unqualifiedTypes.put(unqualifiedType, qualifiedType); qualifiedTypes.add(qualifiedType); String generic = m.group(2); // Consider importing generic type arguments, recursively c = remainder + (PLAIN_GENERIC_TYPE_PATTERN.matcher(generic).matches() ? generic.substring(0, 1) + ref(generic.substring(1, generic.length() - 1)) + generic.substring(generic.length() - 1) : generic); } // If any of the above tests fail, c will remain the unchanged, // fully qualified type name. result.add(c); } } return result; } private static final Pattern KOTLIN_ARRAY_PATTERN = Pattern.compile("kotlin.Array<([^?>]*)\\?>"); private String patchKotlinClasses(String c) { // [#10768] TODO: Is this the right place to patch these classes? Matcher m; if (isKotlin) { if (c.endsWith("[]")) c = "kotlin.Array<" + patchKotlinClasses(c.substring(0, c.length() - 2)) + "?>"; else if (Byte.class.getName().equals(c)) c = "kotlin.Byte"; else if (Short.class.getName().equals(c)) c = "kotlin.Short"; else if (Integer.class.getName().equals(c)) c = "kotlin.Int"; else if (Long.class.getName().equals(c)) c = "kotlin.Long"; else if (Float.class.getName().equals(c)) c = "kotlin.Float"; else if (Double.class.getName().equals(c)) c = "kotlin.Double"; else if (Boolean.class.getName().equals(c)) c = "kotlin.Boolean"; else if (Character.class.getName().equals(c)) c = "kotlin.Char"; else if (String.class.getName().equals(c)) c = "kotlin.String"; else if (Object.class.getName().equals(c)) c = "kotlin.Any"; else if ((m = KOTLIN_ARRAY_PATTERN.matcher(c)).matches()) c = m.replaceAll("kotlin.Array<" + ref(patchKotlinClasses(m.group(1))) + "?>"); } return c; } }