/*
* ToroDB
* Copyright © 2014 8Kdata Technology (www.8kdata.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.torodb.packaging.config.util;
import com.beust.jcommander.internal.Console;
import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonAnyFormatVisitor;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonMapFormatVisitor;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
import com.google.common.base.CaseFormat;
import com.torodb.packaging.config.annotation.Description;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.ResourceBundle;
public class DescriptionFactoryWrapper extends JsonFormatVisitorWrapper.Base {
private final ResourceBundle resourceBundle;
private final Console console;
private final int tabs;
private final JsonPointer jsonPointer;
public DescriptionFactoryWrapper(ResourceBundle resourceBundle, Console console, int tabs) {
this.resourceBundle = resourceBundle;
this.console = console;
this.tabs = tabs;
this.jsonPointer = JsonPointer.valueOf(null);
}
public DescriptionFactoryWrapper(DescriptionFactoryWrapper descriptionFactoryWrapper,
JsonPointer jsonPointer, SerializerProvider p) {
this.resourceBundle = descriptionFactoryWrapper.resourceBundle;
this.console = descriptionFactoryWrapper.console;
this.tabs = descriptionFactoryWrapper.tabs;
this.jsonPointer = jsonPointer;
setProvider(p);
}
public JsonPointer getJsonPointer() {
return jsonPointer;
}
@Override
public JsonAnyFormatVisitor expectAnyFormat(JavaType type) throws JsonMappingException {
SerializerProvider p = getProvider();
JsonSerializer<Object> s = p.findValueSerializer(type);
s.acceptJsonFormatVisitor(new DescriptionFactoryWrapper(this, getJsonPointer(), p), type);
return super.expectAnyFormat(type);
}
@Override
public JsonArrayFormatVisitor expectArrayFormat(JavaType convertedType) {
final JsonPointer jsonPointer = getJsonPointer();
return new JsonArrayFormatVisitor.Base(getProvider()) {
@Override
public void itemsFormat(JsonFormatVisitable handler, JavaType elementType) throws
JsonMappingException {
SerializerProvider p = getProvider();
JsonSerializer<Object> s = p.findValueSerializer(elementType);
s.acceptJsonFormatVisitor(new DescriptionFactoryWrapper(DescriptionFactoryWrapper.this,
jsonPointer.append(JsonPointer.valueOf("/<index>")), p), elementType);
}
};
}
@Override
public JsonObjectFormatVisitor expectObjectFormat(JavaType convertedType) {
final JsonPointer jsonPointer = getJsonPointer();
return new JsonObjectFormatVisitor.Base(getProvider()) {
@Override
public void property(BeanProperty prop) throws JsonMappingException {
visitProperty(prop, false);
}
@Override
public void optionalProperty(BeanProperty prop) throws JsonMappingException {
visitProperty(prop, true);
}
private void visitProperty(BeanProperty prop, boolean optional) throws JsonMappingException {
JsonPointer propPointer = jsonPointer.append(JsonPointer.valueOf("/" + prop.getName()));
document(propPointer, prop);
SerializerProvider p = getProvider();
JsonSerializer<Object> s = p.findValueSerializer(prop.getType(), prop);
s.acceptJsonFormatVisitor(new DescriptionFactoryWrapper(DescriptionFactoryWrapper.this,
propPointer, p), prop.getType());
}
};
}
private void document(JsonPointer propPointer, BeanProperty prop) {
JavaType type = prop.getType();
if (hasDescription(prop) && !isPrimitive(type) && !type.isEnumType() && !type.isMapLikeType()) {
console.println("");
} else if (isPrimitive(type) || type.isEnumType()) {
printTabs();
console.print(propPointer.toString());
console.print("=");
} else if (type.isMapLikeType()) {
printTabs();
console.print(propPointer.append(JsonPointer.compile("/<string>")).toString());
console.print("=");
type = type.getContentType();
}
if (isPrimitive(type)) {
console.print("<");
console.print(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, type.getRawClass()
.getSimpleName()));
console.print(">");
} else if (type.isEnumType()) {
console.print("<enum:string>");
}
if (hasDescription(prop) && !isPrimitive(type) && !type.isEnumType()) {
printTabs();
}
printDescription(prop);
if (hasDescription(prop) || isPrimitive(type) || type.isEnumType()) {
console.println("");
}
if (hasDescription(prop) && !isPrimitive(type) && !type.isEnumType()) {
console.println("");
}
if (type.isEnumType()) {
for (Field enumField : type.getRawClass().getDeclaredFields()) {
if (!enumField.isEnumConstant()) {
continue;
}
printTabs();
console.print(" - ");
console.print(enumField.getName());
Description enumConstantConfigProperty = enumField.getAnnotation(Description.class);
if (enumConstantConfigProperty != null && enumConstantConfigProperty.value() != null) {
console.print(" # ");
console.print(resourceBundle.getString(enumConstantConfigProperty.value()));
}
console.println("");
}
}
}
private void printTabs() {
for (int tab = 0; tab < tabs; tab++) {
console.print("\t");
}
}
private boolean isPrimitive(JavaType type) {
return type.isPrimitive()
|| type.isTypeOrSubTypeOf(Boolean.class)
|| type.isTypeOrSubTypeOf(Number.class)
|| type.isTypeOrSubTypeOf(String.class)
|| type.isTypeOrSubTypeOf(Character.class)
|| type.isTypeOrSubTypeOf(Date.class);
}
private boolean hasDescription(BeanProperty prop) {
return getDescription(prop) != null;
}
private void printDescription(BeanProperty prop) {
Description description = getDescription(prop);
if (description != null) {
console.print(" # ");
console.print(resourceBundle.getString(description.value()));
}
}
private Description getDescription(BeanProperty prop) {
Description description = prop.getAnnotation(Description.class);
if (description == null) {
description = prop.getType().getRawClass().getAnnotation(Description.class);
}
return description;
}
@Override
public JsonMapFormatVisitor expectMapFormat(JavaType type) throws JsonMappingException {
return new JsonMapFormatVisitor.Base(getProvider());
}
}