com.speedment.generator.translator.AbstractJavaClassTranslator Maven / Gradle / Ivy
Show all versions of generator-deploy Show documentation
/*
*
* 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