package org.javabuilders.layout.mig;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.javabuilders.BuildException;
import org.javabuilders.BuildProcess;
import org.javabuilders.Builder;
import org.javabuilders.BuilderConfig;
import org.javabuilders.IStringLiteralControlConfig;
import org.javabuilders.Node;
import org.javabuilders.TypeDefinition;
import org.javabuilders.handler.AbstractTypeHandler;
import org.javabuilders.handler.ITypeChildrenHandler;
import org.javabuilders.layout.ControlConstraint;
import org.javabuilders.layout.DefaultResize;
import org.javabuilders.layout.Flow;
import org.javabuilders.layout.HAlign;
import org.javabuilders.layout.LayoutCell;
import org.javabuilders.layout.LayoutConstraints;
import org.javabuilders.layout.VAlign;
import org.javabuilders.util.BuilderUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract MigLayout handler that descendants can customize for Swing or SWT
* @author Jacek Furmankiewicz
*/
public abstract class AbstractMigLayoutHandler extends AbstractTypeHandler implements ITypeChildrenHandler {
private final static Map<DefaultResize,String> resizeConstraints = new HashMap<DefaultResize, String>();
protected final static Logger logger = LoggerFactory.getLogger(AbstractMigLayoutHandler.class);
private Class<?> defaultTypeClass = null;
private String defaultTypePropertyName = null;
/**
* Static constructor
*/
static {
resizeConstraints.put(DefaultResize.BOTH, "grow");
resizeConstraints.put(DefaultResize.X_AXIS, "growx");
resizeConstraints.put(DefaultResize.Y_AXIS, "growy");
}
/**
* Constructor
* @param defaultTypeClass The class type to create for default objects represented by string literals
* @param defaultTypePropertyName The default property of the default class to use as the key for the string literal value
*
*/
protected AbstractMigLayoutHandler(Class<?> defaultTypeClass, String defaultTypePropertyName) {
super();
this.defaultTypeClass = defaultTypeClass;
this.defaultTypePropertyName = defaultTypePropertyName;
}
protected abstract void setLayout(BuildProcess result, Node node, Object migLayout) throws BuildException;
protected abstract Object getComponent(BuildProcess result, Node components, String name) throws BuildException;
protected abstract void setLayoutConstraints(Object layout, String constraints) throws BuildException;
protected abstract void setRowConstraints(Object layout, String constraints) throws BuildException;
protected abstract void setColumnConstraints(Object layout, String constraints) throws BuildException;
protected abstract void applyControlConstraints(BuildProcess result, Node node, Node components, Map<String,String> layoutConstraints) throws BuildException;
protected abstract void setControlName(Object control, String name);
/* (non-Javadoc)
* @see org.javabuilders.handler.ITypeHandler#useExistingInstance(org.javabuilders.BuilderConfig, org.javabuilders.BuildResult, org.javabuilders.Node, java.lang.String, java.util.Map, java.lang.Object)
*/
@SuppressWarnings("unchecked")
public final Node useExistingInstance(BuilderConfig config, BuildProcess process,
Node parent, String key, Map<String, Object> typeDefinition,
Object instance) throws BuildException {
Node node = new Node(parent,key,typeDefinition);
node.setMainObject(instance);
//Node components = parent.getChildNode(Builder.CONTENT);
Node components = parent;
//set the actual layout manager on the container - overriden in descendants
setLayout(process, node, node.getMainObject());
String layoutCo = "", rowCo = "", columnCo = "";
//process "layout" value if defined
Map<String, String> layoutConstraints = new LinkedHashMap<String, String>();
if (typeDefinition.containsKey(Builder.LAYOUT)) {
String visualLayout = String.valueOf(typeDefinition.get(Builder.LAYOUT));
LayoutConstraints lo = LayoutConstraints.getParsedLayoutConstraints(visualLayout, MigLayoutCommon.DEFAULT_ROW_COLUMN_CONSTRAINT, MigLayoutCommon.DEFAULT_ROW_COLUMN_CONSTRAINT);
//get general layout constraints
layoutCo = lo.getLayoutConstraints();
for(String row : lo.getRowConstraints()) {
rowCo += " " + row;
}
for(String column : lo.getColumnConstraints()) {
columnCo += " " + column;
}
//each cell's/control's constraint
StringBuilder builder = new StringBuilder();
for(LayoutCell cell : lo.getCells()) {
boolean firstControl = true;
for(ControlConstraint co : cell.getControls()) {
//Object component = SwingBuilderUtils.getComponent(components,String.valueOf(co.getControlName()));
Object component = getNamedComponentOrCreateOne(process, components, co);
if (component == null) {
throw new BuildException("MigLayout unable to find control named \"{0}\" in layout:\n{1}",
co.getControlName(), visualLayout);
}
builder.setLength(0);
builder.append("cell ");
builder.append(cell.getColumnIndex());
builder.append(" ");
builder.append(cell.getRowIndex());
//handle span/flow info - put it on first control only
if (firstControl) {
if (co.getHSpan() > 1 || co.getVSpan() > 1) {
builder.append(" ").append(co.getHSpan())
.append(" ").append(co.getVSpan());
}
if (cell.getFlow() == Flow.VERTICAL) {
builder.append(", flowy, top");
}
firstControl = false;
}
//RESIZE logic
MigLayoutCommon.handleResize(builder, co, TypeDefinition.getDefaultResize(config, component.getClass()),
lo.getAdditionalControlConstraints().get(co.getControlName()));
//handle control alignment
if (co.getHAlign() == HAlign.CENTER) {
builder.append(", alignx center");
} else if (co.getHAlign() == HAlign.RIGHT) {
builder.append(", alignx right");
} else if (co.getHAlign() == HAlign.LEFT) {
builder.append(", alignx left");
}
if (co.getVAlign() == VAlign.MIDDLE) {
builder.append(", aligny center");
} else if (co.getVAlign() == VAlign.BOTTOM) {
builder.append(", aligny bottom");
} else if (co.getVAlign() == VAlign.TOP) {
builder.append(", aligny top");
}
//size group logic
if (co.getSizeGroup() != null) {
if (co.isSizeGroupX()) {
//horizontal size group
builder.append(", sgx ").append(co.getSizeGroup());
} else if (co.isSizeGroupY()) {
//vertical size group
builder.append(", sgy ").append(co.getSizeGroup());
} else {
//regular size group
builder.append(", sg ").append(co.getSizeGroup());
}
}
//additional constraints
if (lo.getAdditionalControlConstraints().containsKey(co.getControlName())) {
String constraints = lo.getAdditionalControlConstraints().get(co.getControlName());
//could point to a global String
if (constraints.matches(BuilderConfig.GLOBAL_VARIABLE_REGEX)) {
constraints = (String) config.getGlobalVariable(constraints, String.class);
}
builder.append(", ").append(constraints);
}
layoutConstraints.put(co.getControlName(), builder.toString());
}
}
}
//process the actual layout controls
if (typeDefinition.containsKey(MigLayoutCommon.LAYOUT_CONSTRAINTS)) {
layoutCo += " " + typeDefinition.get(MigLayoutCommon.LAYOUT_CONSTRAINTS);
}
if (typeDefinition.containsKey(MigLayoutCommon.ROW_CONSTRAINTS)) {
rowCo += " " + typeDefinition.get(MigLayoutCommon.ROW_CONSTRAINTS);
}
if (typeDefinition.containsKey(MigLayoutCommon.COLUMN_CONSTRAINTS)) {
columnCo += " " + typeDefinition.get(MigLayoutCommon.COLUMN_CONSTRAINTS);
}
if (layoutCo.length() > 0) {
setLayoutConstraints(instance, layoutCo);
if (logger.isDebugEnabled()) {
logger.debug("MigLayout constraints: " + layoutCo);
}
}
if (rowCo.length() > 0) {
setRowConstraints(instance,rowCo);
if (logger.isDebugEnabled()) {
logger.debug("MigLayout row constraints: " + rowCo);
}
}
if (columnCo.length() > 0) {
setColumnConstraints(instance, columnCo);
if (logger.isDebugEnabled()) {
logger.debug("MigLayout column constraints: " + columnCo);
}
}
//process each child control
if (typeDefinition.containsKey(Builder.CONSTRAINTS)) {
List<? extends Object> constraints = (List<? extends Object>)typeDefinition.get(Builder.CONSTRAINTS);
for(Object constraint : constraints) {
if (constraint instanceof String) {
//constraint = control name (e.g. just "- buttonCancel"
if (!layoutConstraints.containsKey(constraint)) {
layoutConstraints.put(String.valueOf(constraint), "");
}
} else if (constraint instanceof Map){
Map<String,Object> map = (Map<String,Object>)constraint;
for(String componentName : map.keySet()) {
Object componentConstraint = map.get(componentName);
String existingConstraint = layoutConstraints.get(componentName);
if (existingConstraint == null) {
existingConstraint = String.valueOf(componentConstraint);
} else {
existingConstraint += ", " + String.valueOf(componentConstraint);
}
layoutConstraints.put(componentName, existingConstraint);
}
}
}
}
//process
applyControlConstraints(process, node, components, layoutConstraints);
return node;
}
/* (non-Javadoc)
* @see org.javabuilders.handler.AbstractTypeHandler#getCollectionPropertyName()
*/
@Override
public String getCollectionPropertyName() {
//if the MigLayout node is a list, automatically "move" the list to the "constraints" node
return Builder.CONSTRAINTS;
}
/* (non-Javadoc)
* @see org.javabuilders.handler.AbstractTypeHandler#getSimpleValuePropertyName()
*/
@Override
public String getSimpleValuePropertyName() {
//if the MigLayout node is a simple String property "move" the value to the "layout" node
return Builder.LAYOUT;
}
/**
* @param process
* @param components
* @param data.getName()
* @return
*/
private Object getNamedComponentOrCreateOne(BuildProcess process, Node components, ControlConstraint co) {
String name = co.getControlName();
Object component = getComponent(process, components, name);
if (component == null) {
//component not found - maybe need to create it from the string literals?
if (name.startsWith("\"") && name.endsWith("\"")) {
//string literal -> means create a brand new component from it
String text = name.replace("\"","");
text = BuilderUtils.handlePotentialHtmlContent(text);
text = process.getBuildResult().getResource(text); //handle internationalization
//special handling of empty names -> sometimes blank labels need to be created
//for complex layouts
if (name.equals("\"\"")) {
name = "blank";
}
//create name from text value, create node & component and add it to the BuildResult
String prefix = null, suffix = null;
if (process.getConfig() instanceof IStringLiteralControlConfig) {
IStringLiteralControlConfig config = (IStringLiteralControlConfig) process.getConfig();
prefix = config.getStringLiteralControlPrefix();
suffix = config.getStringLiteralControlSuffix();
}
name = BuilderUtils.generateName(process.getBuildResult(), name, prefix, suffix);
String compressedYaml = String.format("%s(name=%s,%s=\"%s\")",defaultTypeClass.getSimpleName(),
name, defaultTypePropertyName,text);
component = Builder.createControlFromCompressedYaml(process, components, compressedYaml);
co.setControlName(name);
setControlName(component, name);
/*
Yaml yaml = new Yaml();
Object value = yaml.load(String.format("%s(%s=%s)",defaultTypeClass.getSimpleName(),defaultTypePropertyName,name));
value = BuilderPreProcessor.preprocess(process.getConfig(), process, value, null);
ITypeHandler handler = TypeDefinition.getTypeHandler(process.getConfig(), defaultTypeClass);
Map<String, Object> typeDefinition = (Map<String, Object>) value;
typeDefinition.put(defaultTypePropertyName, text);
//create name from text value, create node & component and add it to the BuildResult
String prefix = null, suffix = null;
if (process.getConfig() instanceof IStringLiteralControlConfig) {
IStringLiteralControlConfig config = (IStringLiteralControlConfig) process.getConfig();
prefix = config.getStringLiteralControlPrefix();
suffix = config.getStringLiteralControlSuffix();
}
name = BuilderUtils.generateName(process.getBuildResult(), name, prefix, suffix);
Node newNode = handler.createNewInstance(process.getConfig(), process, components, defaultTypeClass.getSimpleName(), typeDefinition);
component = newNode.getMainObject();
co.setControlName(name);
setControlName(component, name);
process.getBuildResult().put(name, component);
IPropertyHandler propHandler = TypeDefinition.getPropertyHandler(process.getConfig(),defaultTypeClass, defaultTypePropertyName);
propHandler.handle(process.getConfig(), process, newNode, defaultTypePropertyName);
*/
} else {
//auto-create controls based on name
component = Builder.buildControlFromName(process, components, name);
}
}
return component;
}
}