/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cayenne.modeler.dialog.codegen;
import org.apache.cayenne.CayenneException;
import org.apache.cayenne.gen.ArtifactsGenerationMode;
import org.apache.cayenne.gen.ClassGenerationAction;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.map.Embeddable;
import org.apache.cayenne.map.EmbeddableAttribute;
import org.apache.cayenne.map.EmbeddedAttribute;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
import org.apache.cayenne.modeler.Application;
import org.apache.cayenne.modeler.dialog.pref.GeneralPreferences;
import org.apache.cayenne.modeler.pref.DataMapDefaults;
import org.apache.cayenne.modeler.pref.FSPath;
import org.apache.cayenne.modeler.util.CayenneController;
import org.apache.cayenne.modeler.util.CodeValidationUtil;
import org.apache.cayenne.swing.BindingBuilder;
import org.apache.cayenne.util.Util;
import org.apache.cayenne.validation.BeanValidationFailure;
import org.apache.cayenne.validation.SimpleValidationFailure;
import org.apache.cayenne.validation.ValidationFailure;
import org.apache.cayenne.validation.ValidationResult;
import org.apache.commons.collections.Predicate;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.prefs.Preferences;
/**
* A mode-specific part of the code generation dialog.
*
*/
public abstract class GeneratorController extends CayenneController {
protected String mode = ArtifactsGenerationMode.ALL.getLabel();
protected Map<DataMap, DataMapDefaults> mapPreferences;
private String outputPath;
public GeneratorController(CodeGeneratorControllerBase parent) {
super(parent);
createDefaults();
createView();
initBindings(new BindingBuilder(getApplication().getBindingFactory(), this));
}
public String getOutputPath() {
return outputPath;
}
public void setOutputPath(String path) {
String old = this.outputPath;
this.outputPath = path;
if (this.outputPath != null && !this.outputPath.equals(old)) {
updatePreferences(path);
}
}
public void updatePreferences(String path) {
if (mapPreferences == null)
return;
Set<DataMap> keys = mapPreferences.keySet();
for (DataMap key : keys) {
mapPreferences
.get(key)
.setOutputPath(path);
}
}
public void setMapPreferences(Map<DataMap, DataMapDefaults> mapPreferences) {
this.mapPreferences = mapPreferences;
}
public Map<DataMap, DataMapDefaults> getMapPreferences() {
return this.mapPreferences;
}
protected void initBindings(BindingBuilder bindingBuilder) {
initOutputFolder();
JTextField outputFolder = ((GeneratorControllerPanel) getView()).getOutputFolder();
JButton outputSelect = ((GeneratorControllerPanel) getView()).getSelectOutputFolder();
outputFolder.setText(getOutputPath());
bindingBuilder.bindToAction(outputSelect, "selectOutputFolderAction()");
bindingBuilder.bindToTextField(outputFolder, "outputPath");
}
protected CodeGeneratorControllerBase getParentController() {
return (CodeGeneratorControllerBase) getParent();
}
protected abstract GeneratorControllerPanel createView();
protected abstract void createDefaults();
/**
* Creates an appropriate subclass of {@link ClassGenerationAction},
* returning it in an unconfigured state. Configuration is performed by
* {@link #createGenerator()} method.
*/
protected abstract ClassGenerationAction newGenerator();
/**
* Creates a class generator for provided selections.
*/
public Collection<ClassGenerationAction> createGenerator() {
File outputDir = getOutputDir();
// no destination folder
if (outputDir == null) {
JOptionPane.showMessageDialog(this.getView(), "Select directory for source files.");
return null;
}
// no such folder
if (!outputDir.exists() && !outputDir.mkdirs()) {
JOptionPane.showMessageDialog(this.getView(), "Can't create directory " + outputDir
+ ". Select a different one.");
return null;
}
// not a directory
if (!outputDir.isDirectory()) {
JOptionPane.showMessageDialog(this.getView(), outputDir + " is not a valid directory.");
return null;
}
// remove generic entities...
Collection<ObjEntity> selectedEntities = new ArrayList<>(getParentController().getSelectedEntities());
Iterator<ObjEntity> it = selectedEntities.iterator();
while (it.hasNext()) {
if (it.next().isGeneric()) {
it.remove();
}
}
Collection<ClassGenerationAction> generators = new ArrayList<>();
Collection<StandardPanelComponent> dataMapLines = ((GeneratorControllerPanel) getView()).getDataMapLines();
for (DataMap map : getParentController().getDataMaps()) {
try {
ClassGenerationAction generator = newGenerator();
generator.setArtifactsGenerationMode(mode);
generator.setDataMap(map);
LinkedList<ObjEntity> objEntities = new LinkedList<>(map.getObjEntities());
objEntities.retainAll(selectedEntities);
generator.addEntities(objEntities);
LinkedList<Embeddable> embeddables = new LinkedList<>(map.getEmbeddables());
embeddables.retainAll(getParentController().getSelectedEmbeddables());
generator.addEmbeddables(embeddables);
generator.addQueries(map.getQueryDescriptors());
Preferences preferences = application.getPreferencesNode(GeneralPreferences.class, "");
if (preferences != null) {
generator.setEncoding(preferences.get(GeneralPreferences.ENCODING_PREFERENCE, null));
}
generator.setDestDir(outputDir);
generator.setMakePairs(true);
for (StandardPanelComponent dataMapLine : dataMapLines) {
if (dataMapLine.getDataMap() == map && !Util.isEmptyString(dataMapLine.getSuperclassPackage().getText())) {
generator.setSuperPkg(dataMapLine.getSuperclassPackage().getText());
break;
}
}
generators.add(generator);
} catch (CayenneException exception) {
JOptionPane.showMessageDialog(this.getView(), exception.getUnlabeledMessage());
return null;
}
}
return generators;
}
public void validateEmbeddable(ValidationResult validationBuffer, Embeddable embeddable) {
ValidationFailure embeddableFailure = validateEmbeddable(embeddable);
if (embeddableFailure != null) {
validationBuffer.addFailure(embeddableFailure);
return;
}
for (EmbeddableAttribute attribute : embeddable.getAttributes()) {
ValidationFailure failure = validateEmbeddableAttribute(attribute);
if (failure != null) {
validationBuffer.addFailure(failure);
return;
}
}
}
private ValidationFailure validateEmbeddableAttribute(EmbeddableAttribute attribute) {
String name = attribute.getEmbeddable().getClassName();
ValidationFailure emptyName = BeanValidationFailure.validateNotEmpty(name, "attribute.name",
attribute.getName());
if (emptyName != null) {
return emptyName;
}
ValidationFailure badName = CodeValidationUtil.validateJavaIdentifier(name, "attribute.name",
attribute.getName());
if (badName != null) {
return badName;
}
ValidationFailure emptyType = BeanValidationFailure.validateNotEmpty(name, "attribute.type",
attribute.getType());
if (emptyType != null) {
return emptyType;
}
ValidationFailure badType = BeanValidationFailure.validateJavaClassName(name, "attribute.type",
attribute.getType());
if (badType != null) {
return badType;
}
return null;
}
protected ValidationFailure validateEmbeddable(Embeddable embeddable) {
String name = embeddable.getClassName();
ValidationFailure emptyClass = BeanValidationFailure.validateNotEmpty(name, "className",
embeddable.getClassName());
if (emptyClass != null) {
return emptyClass;
}
ValidationFailure badClass = BeanValidationFailure.validateJavaClassName(name, "className",
embeddable.getClassName());
if (badClass != null) {
return badClass;
}
return null;
}
public void validateEntity(ValidationResult validationBuffer, ObjEntity entity, boolean clientValidation) {
ValidationFailure entityFailure = validateEntity(clientValidation ? entity.getClientEntity() : entity);
if (entityFailure != null) {
validationBuffer.addFailure(entityFailure);
return;
}
for (ObjAttribute attribute : entity.getAttributes()) {
if (attribute instanceof EmbeddedAttribute) {
EmbeddedAttribute embeddedAttribute = (EmbeddedAttribute) attribute;
for (ObjAttribute subAttribute : embeddedAttribute.getAttributes()) {
ValidationFailure failure = validateEmbeddedAttribute(subAttribute);
if (failure != null) {
validationBuffer.addFailure(failure);
return;
}
}
} else {
ValidationFailure failure = validateAttribute(attribute);
if (failure != null) {
validationBuffer.addFailure(failure);
return;
}
}
}
for (ObjRelationship rel : entity.getRelationships()) {
ValidationFailure failure = validateRelationship(rel, clientValidation);
if (failure != null) {
validationBuffer.addFailure(failure);
return;
}
}
}
protected ValidationFailure validateEntity(ObjEntity entity) {
String name = entity.getName();
if (entity.isGeneric()) {
return new SimpleValidationFailure(name, "Generic class");
}
ValidationFailure emptyClass = BeanValidationFailure.validateNotEmpty(name, "className", entity.getClassName());
if (emptyClass != null) {
return emptyClass;
}
ValidationFailure badClass = BeanValidationFailure.validateJavaClassName(name, "className",
entity.getClassName());
if (badClass != null) {
return badClass;
}
if (entity.getSuperClassName() != null) {
ValidationFailure badSuperClass = BeanValidationFailure.validateJavaClassName(name, "superClassName",
entity.getSuperClassName());
if (badSuperClass != null) {
return badSuperClass;
}
}
return null;
}
protected ValidationFailure validateAttribute(ObjAttribute attribute) {
String name = attribute.getEntity().getName();
ValidationFailure emptyName = BeanValidationFailure.validateNotEmpty(name, "attribute.name",
attribute.getName());
if (emptyName != null) {
return emptyName;
}
ValidationFailure badName = CodeValidationUtil.validateJavaIdentifier(name, "attribute.name",
attribute.getName());
if (badName != null) {
return badName;
}
ValidationFailure emptyType = BeanValidationFailure.validateNotEmpty(name, "attribute.type",
attribute.getType());
if (emptyType != null) {
return emptyType;
}
ValidationFailure badType = BeanValidationFailure.validateJavaClassName(name, "attribute.type",
attribute.getType());
if (badType != null) {
return badType;
}
return null;
}
protected ValidationFailure validateEmbeddedAttribute(ObjAttribute attribute) {
String name = attribute.getEntity().getName();
// validate embeddedAttribute and attribute names
// embeddedAttribute returned attibute as
// [name_embeddedAttribute].[name_attribute]
String[] attributes = attribute.getName().split("\\.");
String nameEmbeddedAttribute = attributes[0];
int beginIndex = attributes[0].length();
String attr = attribute.getName().substring(beginIndex + 1);
ValidationFailure emptyEmbeddedName = BeanValidationFailure.validateNotEmpty(name, "attribute.name",
nameEmbeddedAttribute);
if (emptyEmbeddedName != null) {
return emptyEmbeddedName;
}
ValidationFailure badEmbeddedName = CodeValidationUtil.validateJavaIdentifier(name, "attribute.name",
nameEmbeddedAttribute);
if (badEmbeddedName != null) {
return badEmbeddedName;
}
ValidationFailure emptyName = BeanValidationFailure.validateNotEmpty(name, "attribute.name", attr);
if (emptyName != null) {
return emptyName;
}
ValidationFailure badName = CodeValidationUtil.validateJavaIdentifier(name, "attribute.name", attr);
if (badName != null) {
return badName;
}
ValidationFailure emptyType = BeanValidationFailure.validateNotEmpty(name, "attribute.type",
attribute.getType());
if (emptyType != null) {
return emptyType;
}
ValidationFailure badType = BeanValidationFailure.validateJavaClassName(name, "attribute.type",
attribute.getType());
if (badType != null) {
return badType;
}
return null;
}
protected ValidationFailure validateRelationship(ObjRelationship relationship, boolean clientValidation) {
String name = relationship.getSourceEntity().getName();
ValidationFailure emptyName = BeanValidationFailure.validateNotEmpty(name, "relationship.name",
relationship.getName());
if (emptyName != null) {
return emptyName;
}
ValidationFailure badName = CodeValidationUtil.validateJavaIdentifier(name, "relationship.name",
relationship.getName());
if (badName != null) {
return badName;
}
if (!relationship.isToMany()) {
ObjEntity targetEntity = relationship.getTargetEntity();
if (clientValidation && targetEntity != null) {
targetEntity = targetEntity.getClientEntity();
}
if (targetEntity == null) {
return new BeanValidationFailure(name, "relationship.targetEntity", "No target entity");
} else if (!targetEntity.isGeneric()) {
ValidationFailure emptyClass = BeanValidationFailure.validateNotEmpty(name,
"relationship.targetEntity.className", targetEntity.getClassName());
if (emptyClass != null) {
return emptyClass;
}
ValidationFailure badClass = BeanValidationFailure.validateJavaClassName(name,
"relationship.targetEntity.className", targetEntity.getClassName());
if (badClass != null) {
return badClass;
}
}
}
return null;
}
/**
* Returns a predicate for default entity selection in a given mode.
*/
public Predicate getDefaultClassFilter() {
final ObjEntity selectedEntity = Application.getInstance().getFrameController().getProjectController()
.getCurrentObjEntity();
final Embeddable selectedEmbeddable = Application.getInstance().getFrameController().getProjectController()
.getCurrentEmbeddable();
// select a single entity
if (selectedEntity != null) {
final boolean hasProblem = getParentController().getProblem(selectedEntity.getName()) != null;
return new Predicate() {
public boolean evaluate(Object object) {
return !hasProblem && object == selectedEntity;
}
};
}
// select a single embeddable
else if (selectedEmbeddable != null) {
final boolean hasProblem = getParentController().getProblem(selectedEmbeddable.getClassName()) != null;
return new Predicate() {
public boolean evaluate(Object object) {
return !hasProblem && object == selectedEmbeddable;
}
};
}
// select all entities
else {
return new Predicate() {
public boolean evaluate(Object object) {
if (object instanceof ObjEntity) {
return getParentController().getProblem(((ObjEntity) object).getName()) == null;
}
if (object instanceof Embeddable) {
return getParentController().getProblem(((Embeddable) object).getClassName()) == null;
}
return false;
}
};
}
}
public File getOutputDir() {
String dir = ((GeneratorControllerPanel) getView()).getOutputFolder().getText();
return dir != null ? new File(dir) : new File(System.getProperty("user.dir"));
}
/**
* An action method that pops up a file chooser dialog to pick the
* generation directory.
*/
public void selectOutputFolderAction() {
JTextField outputFolder = ((GeneratorControllerPanel) getView()).getOutputFolder();
String currentDir = outputFolder.getText();
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
chooser.setDialogType(JFileChooser.OPEN_DIALOG);
// guess start directory
if (!Util.isEmptyString(currentDir)) {
chooser.setCurrentDirectory(new File(currentDir));
} else {
FSPath lastDir = Application.getInstance().getFrameController().getLastDirectory();
lastDir.updateChooser(chooser);
}
int result = chooser.showOpenDialog(getView());
if (result == JFileChooser.APPROVE_OPTION) {
File selected = chooser.getSelectedFile();
// update model
String path = selected.getAbsolutePath();
outputFolder.setText(path);
setOutputPath(path);
}
}
private void initOutputFolder() {
String path;
if (getOutputPath() == null) {
if (System.getProperty("cayenne.cgen.destdir") != null) {
setOutputPath(System.getProperty("cayenne.cgen.destdir"));
} else {
// init default directory..
FSPath lastPath = Application.getInstance().getFrameController().getLastDirectory();
path = checkDefaultMavenResourceDir(lastPath, "test");
if (path != null || (path = checkDefaultMavenResourceDir(lastPath, "main")) != null) {
setOutputPath(path);
} else {
File lastDir = (lastPath != null) ? lastPath.getExistingDirectory(false) : null;
setOutputPath(lastDir != null ? lastDir.getAbsolutePath() : null);
}
}
}
}
private String checkDefaultMavenResourceDir(FSPath lastPath, String dirType) {
String path = lastPath.getPath();
String resourcePath = buildFilePath("src", dirType, "resources");
int idx = path.indexOf(resourcePath);
if (idx < 0) {
return null;
}
return path.substring(0, idx) + buildFilePath("src", dirType, "java");
}
private static String buildFilePath(String... pathElements) {
if (pathElements.length == 0) {
return "";
}
StringBuilder path = new StringBuilder(pathElements[0]);
for (int i = 1; i < pathElements.length; i++) {
path.append(File.separator).append(pathElements[i]);
}
return path.toString();
}
}