All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.speedment.generator.translator.AbstractJavaClassTranslator Maven / Gradle / Ivy

Go to download

A Speedment bundle that shades all dependencies into one jar. This is useful when deploying an application on a server.

The newest version!
/*
 *
 * Copyright (c) 2006-2019, 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.annotation.GeneratedCode;
import com.speedment.common.codegen.Generator;
import static com.speedment.common.codegen.constant.DefaultJavadocTag.AUTHOR;
import com.speedment.common.codegen.controller.AlignTabs;
import com.speedment.common.codegen.controller.AutoImports;
import com.speedment.common.codegen.model.AnnotationUsage;
import com.speedment.common.codegen.model.Class;
import com.speedment.common.codegen.model.ClassOrInterface;
import com.speedment.common.codegen.model.Constructor;
import com.speedment.common.codegen.model.Enum;
import com.speedment.common.codegen.model.Field;
import com.speedment.common.codegen.model.File;
import com.speedment.common.codegen.model.Interface;
import com.speedment.common.codegen.model.Javadoc;
import com.speedment.common.codegen.model.Value;
import com.speedment.common.injector.Injector;
import com.speedment.common.injector.annotation.Inject;
import com.speedment.common.mapstream.MapStream;
import com.speedment.generator.translator.component.TypeMapperComponent;
import com.speedment.runtime.config.*;
import com.speedment.runtime.config.internal.*;
import com.speedment.runtime.config.trait.HasEnabled;
import com.speedment.runtime.config.trait.HasId;
import com.speedment.runtime.config.trait.HasMainInterface;
import com.speedment.runtime.config.trait.HasName;
import com.speedment.runtime.core.component.InfoComponent;
import java.lang.reflect.Type;
import java.util.*;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toSet;

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;

/**
 *
 * @param   document type.
 * @param     Java type (Interface, Class or Enum) to generate
 * 
 * @author  Per Minborg
 */
public abstract class AbstractJavaClassTranslator>
    implements JavaClassTranslator {

    public static final String GETTER_METHOD_PREFIX = "get";
    public static final String SETTER_METHOD_PREFIX = "set";
    public static final String FINDER_METHOD_PREFIX = "find";
    public static final String JAVADOC_MESSAGE
        = "\n

\nThis file is safe to edit. It will not be overwritten by the " + "code generator."; @Inject private Generator generator; @Inject private InfoComponent infoComponent; @Inject private TypeMapperComponent typeMappers; @Inject private Injector injector; private final D document; private final Function mainModelConstructor; private final List>> listeners; protected AbstractJavaClassTranslator(D document, Function mainModelConstructor) { this.document = requireNonNull(document); this.mainModelConstructor = requireNonNull(mainModelConstructor); this.listeners = new CopyOnWriteArrayList<>(); } @Override public final TranslatorSupport getSupport() { return new TranslatorSupport<>(injector, document); } @Override public final D getDocument() { return document; } protected AnnotationUsage generated() { final String owner = infoComponent.getTitle(); return AnnotationUsage.of(GeneratedCode.class).set(Value.ofText(owner)); } /** * Returns the name of the {@link Class}/{@link Interface}/{@link Enum} that * this translator will create. The name is only the short name. It does not * include package information. *

* Example: {@code HareImpl} * * @return the short filename of the file to generate */ protected abstract String getClassOrInterfaceName(); /** * Creates and configures a {@link Class}/{@link Interface}/{@link Enum} for * the specified {@code File}. This method uses a builder created using the * {@link #newBuilder(File, String)} method to make sure all the listeners * are notified when the model is built. *

* Observe that this method doesn't add the created model to the file. * * @param file the file to make a model for * @return the model made */ protected abstract T makeCodeGenModel(File file); /** * This method is executed after the file has been created but before it is * returned to the code generator. * * @param file the file to operate on */ protected void finializeFile(File file) { // Do nothing } @Override public File get() { final File file = File.of(getSupport().baseDirectoryName() + "/" + (isInGeneratedPackage() ? "generated/" : "") + getClassOrInterfaceName() + ".java" ); final T item = makeCodeGenModel(file); if (!item.getJavadoc().isPresent()) { item.set(getJavaDoc()); } file.add(item); finializeFile(file); file.call(new AutoImports(getCodeGenerator().getDependencyMgr())); file.call(new AlignTabs<>()); return file; } protected abstract String getJavadocRepresentText(); protected Javadoc getJavaDoc() { final String owner; final String message; if (isInGeneratedPackage()) { owner = infoComponent.getTitle(); message = getGeneratedJavadocMessage(); } else { owner = project().orElseThrow(NoSuchElementException::new).getCompanyName(); message = JAVADOC_MESSAGE; } return Javadoc.of(getJavadocRepresentText() + message) .add(AUTHOR.setValue(owner)); } @Override public Generator getCodeGenerator() { return generator; } @Override public boolean isInGeneratedPackage() { return false; } @Override public void onMake(BiConsumer> action) { listeners.add(action); } @Override public Stream>> listeners() { return listeners.stream(); } protected final class BuilderImpl implements Translator.Builder { private static final String PROJECTS = "projects"; private final String name; private final Map>>> map; // Special for this case private final Map>> foreignKeyReferencesThisTableConsumers; public BuilderImpl(String name) { this.name = requireNonNull(name); // Phase stuff this.map = new EnumMap<>(Phase.class); this.foreignKeyReferencesThisTableConsumers = new EnumMap<>(Phase.class); for (final Phase phase : Phase.values()) { map.put(phase, new HashMap<>()); foreignKeyReferencesThisTableConsumers.put(phase, new ArrayList<>()); } } @Override public

Builder forEvery(Phase phase, String key, BiFunction, D> constructor, BiConsumer consumer) { aquireListAndAdd(phase, key, wrap(consumer, constructor)); return this; } @Override public Builder forEveryProject(Phase phase, BiConsumer consumer) { aquireListAndAdd(phase, PROJECTS, wrap(consumer, ProjectImpl::new)); return this; } @Override public Builder forEveryDbms(Phase phase, BiConsumer consumer) { aquireListAndAdd(phase, ProjectUtil.DBMSES, wrap(consumer, DbmsImpl::new)); return this; } @Override public Builder forEverySchema(Phase phase, BiConsumer consumer) { aquireListAndAdd(phase, DbmsUtil.SCHEMAS, wrap(consumer, SchemaImpl::new)); return this; } @Override public Builder forEveryTable(Phase phase, BiConsumer consumer) { aquireListAndAdd(phase, SchemaUtil.TABLES, wrap(consumer, TableImpl::new)); return this; } @Override public Builder forEveryColumn(Phase phase, BiConsumer consumer) { aquireListAndAdd(phase, TableUtil.COLUMNS, wrap(consumer, ColumnImpl::new)); return this; } @Override public Builder forEveryIndex(Phase phase, BiConsumer consumer) { aquireListAndAdd(phase, TableUtil.INDEXES, wrap(consumer, IndexImpl::new)); return this; } @Override public Builder forEveryForeignKey(Phase phase, BiConsumer consumer) { aquireListAndAdd(phase, TableUtil.FOREIGN_KEYS, wrap(consumer, ForeignKeyImpl::new)); return this; } private

BiConsumer wrap(BiConsumer consumer, BiFunction, D> constructor) { return (t, doc) -> { @SuppressWarnings("unchecked") final P parent = (P) doc.getParent().orElse(null); consumer.accept(t, constructor.apply(parent, doc.getData())); }; } @Override public Builder forEveryForeignKeyReferencingThis(Phase phase, BiConsumer consumer) { foreignKeyReferencesThisTableConsumers.get(phase).add(requireNonNull(consumer)); return this; } @SuppressWarnings("unchecked") private void aquireListAndAdd(Phase phase, String key, BiConsumer consumer) { aquireList(phase, key).add(requireNonNull(consumer)); } @SuppressWarnings("unchecked") private List> aquireList(Phase phase, String key) { return (List>) (List) map.get(phase).computeIfAbsent(key, unused -> new CopyOnWriteArrayList<>()); } private void act(Phase phase, String key, T item, D document) { aquireList(phase, key).forEach(c -> c.accept(requireNonNull(item), requireNonNull(document)) ); } @Override public T build() { final T model = mainModelConstructor.apply(name); if (isInGeneratedPackage()) { model.add(generated()); } for (Phase phase : Phase.values()) { project().ifPresent(p -> act(phase, PROJECTS, model, p)); dbms().ifPresent(d -> act(phase, ProjectUtil.DBMSES, model, d)); schema().ifPresent(s -> act(phase, DbmsUtil.SCHEMAS, model, s)); table().ifPresent(t -> act(phase, SchemaUtil.TABLES, model, t)); MapStream.of(map.get(phase)) .flatMapValue(List::stream) .forEachOrdered((key, actor) -> table() .ifPresent(table -> MapStream.of(table.getData()) .filterKey(key::equals) // Filter out elements that map to a list and flat // map the stream so that every value in the list // becomes an element of the stream. .filterValue(List.class::isInstance) .mapValue(v -> { @SuppressWarnings("unchecked") final List> val = (List>) v; return val; }) .flatMapValue(List::stream) // The foreignKeys-property is special in that only // keys that reference enabled and existing table // and columns are to be included. .filter((k, v) -> { if (TableUtil.FOREIGN_KEYS.equals(k)) { return new ForeignKeyImpl(table, v) .foreignKeyColumns() .map(ForeignKeyColumn::findColumn) .allMatch(c -> c.filter(Column::isEnabled) .map(Column::getParentOrThrow) .filter(Table::isEnabled) .isPresent() ); // Indexes, PrimaryKeyColumns and Columns should // be handled as usual. } else return true; }) // We are now done with the keys. Use the values of // the stream to produce an ordinary stream of base // documents. .values() .map(data -> new BaseDocument(table, data)) // All the documents that are enabled should be // passed to the actor. .filter(HasEnabled::test) .forEachOrdered(c -> actor.accept(model, c)) ) ); if (Table.class.equals(getDocument().mainInterface())) { schema().ifPresent(schema -> schema.tables() .filter(HasEnabled::test) .flatMap(Table::foreignKeys) .filter(HasEnabled::test) .filter(fk -> fk.foreignKeyColumns() .filter(fkc -> fkc.getForeignTableName().equals(getDocument().getId())) .filter(HasEnabled::test) .anyMatch(fkc -> fkc.findForeignColumn().map(HasEnabled::test).orElse(false)) ).forEachOrdered(fk -> foreignKeyReferencesThisTableConsumers.get(phase).forEach( c -> c.accept(model, fk) ) ) ); } // Traverse downwards, Fix #224 namedChildren(document) .forEach(e -> processDocument(model, phase, e.getKey(), e.getValue())); } return model; } private Stream> namedChildren(Document doc) { @SuppressWarnings("unchecked") Stream> s = doc.getData() .entrySet() .stream() .filter(e -> e.getValue() instanceof List) .flatMap(e -> ((List>) e.getValue()).stream().map(m -> new AbstractMap.SimpleEntry<>(e.getKey(), new BaseDocument(document, m)))); return s.filter(e -> HasEnabled.test(e.getValue())); } private final Set aboveTable = Stream.of( ProjectUtil.DBMSES, DbmsUtil.SCHEMAS, SchemaUtil.TABLES ) .collect(toSet()); private void processDocument(T model, Phase phase, String key, Document doc) { // Recursively invoke on the children if (aboveTable.contains(key)) { /*System.out.println(model + " " + phase + " " + key);*/ namedChildren(doc) .forEach(e -> // Tables and below has already been handled. processDocument(model, phase, e.getKey(), e.getValue()) ); // Process the document if in right phase and for right key (type of document) map .getOrDefault(phase, emptyMap()) .getOrDefault(key, emptyList()) .forEach(c -> c.accept(model, doc)); } } } protected final Builder newBuilder(File file, String className) { requireNonNull(file); requireNonNull(className); final Builder builder = new BuilderImpl(className); listeners().forEachOrdered(action -> action.accept(file, builder)); return builder; } public Field fieldFor(Column c) { return Field.of( getSupport().variableName(c), typeMappers.get(c).getJavaType(c) ); } public Constructor emptyConstructor() { return Constructor.of().public_(); } public enum CopyConstructorMode { SETTER, FIELD } public Constructor copyConstructor(Type type, CopyConstructorMode mode) { final TranslatorSupport support = getSupport(); final Constructor constructor = Constructor.of().protected_() .add(Field.of(support.variableName(), type)); columns().forEachOrdered(c -> { switch (mode) { case FIELD: { constructor.add( "this." + support.variableName(c) + " = " + support.variableName() + "." + GETTER_METHOD_PREFIX + support.typeName(c) + "();"); break; } case SETTER: { if (c.isNullable()) { constructor.add( support.variableName() + "." + GETTER_METHOD_PREFIX + support.typeName(c) + "().ifPresent(this::" + SETTER_METHOD_PREFIX + support.typeName(c) + ");" ); } else { constructor.add( SETTER_METHOD_PREFIX + support.typeName(c) + "(" + support.variableName() + ".get" + support.typeName(c) + "());" ); } break; } default: throw new UnsupportedOperationException( "Unknown mode '" + mode + "'." ); } }); return constructor; } protected String getGeneratedJavadocMessage() { return "\n

\nThis file has been automatically generated by " + infoComponent.getTitle() + ". Any changes made to it will be overwritten."; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy