/** * * Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); You may not * use this file except in compliance with the License. You may obtain a copy of * the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.speedment.generator.translator; import com.speedment.common.codegen.constant.SimpleType; import com.speedment.common.injector.Injector; import com.speedment.generator.translator.component.TypeMapperComponent; import com.speedment.generator.translator.namer.JavaLanguageNamer; import com.speedment.runtime.config.*; import com.speedment.runtime.config.trait.HasAlias; import com.speedment.runtime.config.trait.HasMainInterface; import com.speedment.runtime.config.trait.HasName; import com.speedment.runtime.config.trait.HasPackageName; import com.speedment.runtime.config.util.DocumentUtil; import com.speedment.runtime.core.exception.SpeedmentException; import java.lang.reflect.Type; import java.util.Optional; import java.util.function.Supplier; import static com.speedment.common.codegen.util.Formatting.shortName; import static com.speedment.common.codegen.util.Formatting.ucfirst; import static com.speedment.runtime.config.Project.DEFAULT_PACKAGE_NAME; import static com.speedment.runtime.config.util.DocumentUtil.Name.JAVA_NAME; import static java.util.Objects.requireNonNull; /** * A support class for the {@link Translator} interface that holds various * naming methods used in the translator implementations. * <p> * This class might be refactored later on to separate methods that require the * document and those that do not in separate files. * * @param <DOC> the document type * * @author Emil Forslund * @since 2.3.0 */ public final class TranslatorSupport<DOC extends Document & HasName & HasMainInterface> { public final static String IMPL_SUFFIX = "Impl", MANAGER_SUFFIX = "Manager", GENERATED_PACKAGE = "generated", GENERATED_PREFIX = "Generated", SQL_ADAPTER_SUFFIX= "SqlAdapter"; private final DOC document; private final Injector injector; public TranslatorSupport(Injector injector, DOC document) { this.document = requireNonNull(document); this.injector = requireNonNull(injector); } public JavaLanguageNamer namer() { return injector.getOrThrow(JavaLanguageNamer.class); } public Type typeOf(Column column) { return injector.getOrThrow(TypeMapperComponent.class) .get(column).getJavaType(column); } protected DOC document() { return document; } public String entityName() { return shortName(entityType().getTypeName()); } public String entityImplName() { return shortName(entityImplType().getTypeName()); } public String generatedEntityName() { return shortName(generatedEntityType().getTypeName()); } public String generatedEntityImplName() { return shortName(generatedEntityImplType().getTypeName()); } public String managerName() { return shortName(managerType().getTypeName()); } public String managerImplName() { return shortName(managerImplType().getTypeName()); } public String generatedManagerName() { return shortName(generatedManagerType().getTypeName()); } public String generatedManagerImplName() { return shortName(generatedManagerImplType().getTypeName()); } public String sqlAdapterName() { return shortName(sqlAdapterType().getTypeName()); } public String generatedSqlAdapterName() { return shortName(generatedSqlAdapterType().getTypeName()); } public Type entityType() { return SimpleType.create(fullyQualifiedTypeName()); } public Type entityImplType() { return SimpleType.create(fullyQualifiedTypeName() + IMPL_SUFFIX); } public Type generatedEntityType() { return SimpleType.create(fullyQualifiedTypeName(GENERATED_PACKAGE, GENERATED_PREFIX)); } public Type generatedEntityImplType() { return SimpleType.create(fullyQualifiedTypeName(GENERATED_PACKAGE, GENERATED_PREFIX) + IMPL_SUFFIX); } public Type managerType() { return SimpleType.create(fullyQualifiedTypeName() + MANAGER_SUFFIX); } public Type managerImplType() { return SimpleType.create(fullyQualifiedTypeName() + MANAGER_SUFFIX + IMPL_SUFFIX); } public Type generatedManagerType() { return SimpleType.create(fullyQualifiedTypeName(GENERATED_PACKAGE, GENERATED_PREFIX) + MANAGER_SUFFIX); } public Type generatedManagerImplType() { return SimpleType.create(fullyQualifiedTypeName(GENERATED_PACKAGE, GENERATED_PREFIX) + MANAGER_SUFFIX + IMPL_SUFFIX); } public Type sqlAdapterType() { return SimpleType.create(fullyQualifiedTypeName() + SQL_ADAPTER_SUFFIX); } public Type generatedSqlAdapterType() { return SimpleType.create(fullyQualifiedTypeName(GENERATED_PACKAGE, GENERATED_PREFIX) + SQL_ADAPTER_SUFFIX); } /** * Returns the alias of the current document formatted as a java variable. * <p> * Example: * <ul> * <li>{@code employeesSchema} * <li>{@code userTable} * <li>{@code firstname} * </ul> * * @return the document name as a variable * * @see #document() */ public String variableName() { return variableName(HasAlias.of(document)); } /** * Returns the alias of the specified document formatted as a java variable. * <p> * Example: * <ul> * <li>{@code employeesSchema} * <li>{@code userTable} * <li>{@code firstname} * </ul> * * @param doc the document to retrieve the name from. * @return the node name as a variable */ public String variableName(HasAlias doc) { requireNonNull(doc); return namer().javaVariableName(doc.getJavaName()); } /** * Returns the alias of the current document formatted as a java type. * <p> * Example: * <ul> * <li>{@code EmployeesSchema} * <li>{@code UserTable} * <li>{@code Firstname} * </ul> * * @return the document alias as a type * @see #document() */ public String typeName() { return typeName(HasAlias.of(document)); } /** * Returns the alias of the specified document formatted as a java type. * <p> * Example: * <ul> * <li>{@code EmployeesSchema} * <li>{@code UserTable} * <li>{@code Firstname} * </ul> * * @param doc the document to retrieve the alias from * @return the document alias as a type */ public String typeName(HasAlias doc) { return namer().javaTypeName(requireNonNull(doc).getJavaName()); } /** * Returns the name of the specified project formatted as a java type. * <p> * Example: * <ul> * <li>{@code EmployeesSchema} * <li>{@code UserTable} * <li>{@code Firstname} * </ul> * * @param project the document to retrieve the name from * @return the project name as a type */ public String typeName(Project project) { return namer().javaTypeName(requireNonNull(project).getName()); } /** * Returns the alias of the current document as a java type but with the * keyword 'Manager' appended to it. * <p> * Example: * <ul> * <li>{@code EmployeesSchemaManager} * <li>{@code UserTableManager} * <li>{@code FirstnameManager} * </ul> * * @return the document alias as a manager type * @see #document() */ public String managerTypeName() { return managerTypeName(HasAlias.of(document)); } /** * Returns the alias of the specified document as a java type but with the * keyword 'Manager' appended to it. * <p> * Example: * <ul> * <li>{@code EmployeesSchemaManager} * <li>{@code UserTableManager} * <li>{@code FirstnameManager} * </ul> * * @param doc the document to retrieve the alias from * @return the document alias as a manager type */ public String managerTypeName(HasAlias doc) { return typeName(doc) + "Manager"; } /** * Returns the fully qualified type name of the current document. * <p> * Example: * <ul> * <li>{@code com.speedment.example.employeesschema.EmployeesSchema} * <li>{@code com.speedment.example.usertable.UserTable} * <li>{@code com.speedment.example.usertable.firstname.Firstname} * </ul> * <p> * Note that this method is only meant to work with documents at * {@link Table} or higher level in the hierarchy. It will return a * result for all documents located in a valid hierarchy, but the result * might not be as intended. * * @return the fully qualified type name of the current document * @see * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.5.5.2"> * Concerning fully qualified type names * </a> */ public String fullyQualifiedTypeName() { return fullyQualifiedTypeName(null); } /** * Returns the fully qualified type name of the current document. The * specified sub-path will be added after the base package name and before * the type name of the node. The sub-path should not contain either * leading nor trailing dots. * <p> * Example: * <ul> * <li>{@code com.speedment.example.employeesschema.EmployeesSchema} * <li>{@code com.speedment.example.usertable.UserTable} * <li>{@code com.speedment.example.usertable.firstname.Firstname} * </ul> * <p> * Note that this method is only meant to work with nodes at * {@code Table} or higher level in the hierarchy. It will return a * result for all documents located in a valid hierarchy, but the result * might not be as intended. * * @param subPath A sub-path to be added at the end of the 'package'-part * of the qualified type name. This value can be * {@code null} and in that case an ordinary * {@code fullyQualifiedTypeName} will be returned. * @return the fully qualified type name of the current document * @see * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.5.5.2"> * Concerning fully qualified type names * </a> */ public String fullyQualifiedTypeName(String subPath) { return fullyQualifiedTypeName(subPath, ""); } /** * Returns the fully qualified type name of the current document. The * specified sub-path will be added after the base package name and before * the type name of the node. The sub-path should not contain either leading * nor trailing dots. * <p> * Example: * <ul> * <li>{@code com.speedment.example.employeesschema.EmployeesSchema} * <li>{@code com.speedment.example.usertable.UserTable} * <li>{@code com.speedment.example.usertable.firstname.Firstname} * </ul> * <p> * Note that this method is only meant to work with nodes at {@code Table} * or higher level in the hierarchy. It will return a result for all * documents located in a valid hierarchy, but the result might not be as * intended. * * @param subPath a prefix that will be added to the "class"-part of the * type name * @param filePrefix a sub-path to be added at the end of the * 'package'-part of the qualified type name. This value * can be {@code null} and in that case an ordinary * {@code fullyQualifiedTypeName} will be returned. * @return the fully qualified type name of the current document * @see * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.5.5.2"> * Concerning fully qualified type names * </a> */ public String fullyQualifiedTypeName(String subPath, String filePrefix) { requireNonNull(filePrefix); // subPath is nullable if (subPath == null || subPath.isEmpty()) { return basePackageName() + "." + ucfirst(filePrefix) + typeName(HasAlias.of(document)); } else { return basePackageName() + "." + subPath + "." + ucfirst(filePrefix) + typeName(HasAlias.of(document)); } } /** * Returns the base package name of the current node. This is everything up * to but not including the type name. No trailing dot is added. * <p> * If the user has specified a custom package in the config file, that will * be returned. Otherwise a package name will ge generated using the default * settings. * * @return the base package name in lowercase */ public String basePackageName() { try { return Optional.of(document()) .map(HasPackageName.class::cast) .flatMap(HasPackageName::getPackageName) .orElseGet(this::defaultPackageName); } catch (final ClassCastException ex) { throw new SpeedmentException( "The method basePackageName() may only be called on " + "instances of TranslatorSupport that have a document that " + "implements HasPackageName.", ex ); } } /** * Returns the default package name where the document would be located if * the user has not specified a custom package in the config file. * * @return the default package name */ public String defaultPackageName() { final Supplier<String> defaultProjectPackage = () -> DEFAULT_PACKAGE_NAME + namer().javaPackageName(projectOrThrow().getCompanyName()) + "." + namer().javaPackageName(projectOrThrow().getName()); if (document() instanceof Project) { return defaultProjectPackage.get(); } else { return projectOrThrow().getPackageName() .orElseGet(defaultProjectPackage) + "." + DocumentUtil.relativeName( document(), Dbms.class, JAVA_NAME, namer()::javaPackageName ); } } /** * Returns the base directory name of the current node. It is the same as * returned by {@link #basePackageName()} but with dashes ('/') instead of * dots ('.'). * * @return the base package name. */ public String baseDirectoryName() { return basePackageName().replace(".", "/"); } /** * Return this node or any ancestral node that is a {@link Project}. If no * such node exists, an empty {@code Optional} is returned. * * @return the project node */ public Optional<Project> project() { return documentOfType(Project.class); } /** * Return this node or any ancestral node that is a {@link Dbms}. If no such * node exists, an empty {@code Optional} is returned. * * @return the dbms node */ public Optional<Dbms> dbms() { return documentOfType(Dbms.class); } /** * Return this node or any ancestral node that is a {@link Schema}. If no * such node exists, an empty {@code Optional} is returned. * * @return the schema node */ public Optional<Schema> schema() { return documentOfType(Schema.class); } /** * Return this node or any ancestral node that is a {@link Table}. If no * such node exists, an empty {@code Optional} is returned. * * @return the table node */ public Optional<Table> table() { return documentOfType(Table.class); } /** * Return this node or any ancestral node that is a {@link Column}. If no * such node exists, an empty {@code Optional} is returned. * * @return the column node */ public Optional<Column> column() { return documentOfType(Column.class); } /** * Return this node or any ancestral node that is a {@link Project}. If no * such node exists, an {@code IllegalStateException} is thrown. * * @return the project node * @throws IllegalStateException if there was no project */ public Project projectOrThrow() { return project().orElseThrow(() -> new IllegalStateException( getClass().getSimpleName() + " must have a " + Project.class.getSimpleName() + " document." )); } /** * Return this node or any ancestral node that is a {@link Dbms}. If no * such node exists, an {@code IllegalStateException} is thrown. * * @return the dbms node * @throws IllegalStateException if there was no dbms */ public Dbms dbmsOrThrow() { return dbms().orElseThrow(() -> new IllegalStateException( getClass().getSimpleName() + " must have a " + Dbms.class.getSimpleName() + " document." )); } /** * Return this node or any ancestral node that is a {@link Schema}. If no * such node exists, an {@code IllegalStateException} is thrown. * * @return the schema node * @throws IllegalStateException if there was no schema */ public Schema schemaOrThrow() { return schema().orElseThrow(() -> new IllegalStateException( getClass().getSimpleName() + " must have a " + Schema.class.getSimpleName() + " document." )); } /** * Return this node or any ancestral node that is a {@link Table}. If no * such node exists, an {@code IllegalStateException} is thrown. * * @return the table node * @throws IllegalStateException if there was no table */ public Table tableOrThrow() { return table().orElseThrow(() -> new IllegalStateException( getClass().getSimpleName() + " must have a " + Table.class.getSimpleName() + " document." )); } /** * Return this node or any ancestral node that is a {@link Column}. If no * such node exists, an {@code IllegalStateException} is thrown. * * @return the column node * @throws IllegalStateException if there was no column */ public Column columnOrThrow() { return column().orElseThrow(() -> new IllegalStateException( getClass().getSimpleName() + " must have a " + Column.class.getSimpleName() + " document." )); } /** * Returns this node or one of the ancestor nodes if it matches the * specified {@code Class}. If no such node exists, an * {@code IllegalStateException} is thrown. * * @param <E> the type of the class to match * @param clazz the class to match * @return the node found */ private <E extends Document & HasMainInterface> Optional<E> documentOfType(Class<E> clazz) { requireNonNull(clazz); if (clazz.isAssignableFrom(document().mainInterface())) { @SuppressWarnings("unchecked") final E result = (E) document(); return Optional.of(result); } return document() .ancestors() .filter(clazz::isInstance) .map(clazz::cast) .findAny(); } }