package act.cli.view;
/*-
* #%L
* ACT Framework
* %%
* Copyright (C) 2014 - 2017 ActFramework
* %%
* 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.
* #L%
*/
import act.cli.CliContext;
import act.cli.CliOverHttpContext;
import act.cli.ascii_table.impl.CollectionASCIITableAware;
import act.cli.tree.TreeNode;
import act.cli.util.CliCursor;
import act.cli.util.MappedFastJsonNameFilter;
import act.cli.util.TableCursor;
import act.data.DataPropertyRepository;
import act.util.ActContext;
import act.util.DisableFastJsonCircularReferenceDetect;
import act.util.FastJsonPropertyPreFilter;
import act.util.PropertySpec;
import com.alibaba.fastjson.serializer.SerializeFilter;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.osgl.$;
import org.osgl.util.C;
import org.osgl.util.E;
import org.osgl.util.S;
import org.rythmengine.utils.Escape;
import java.util.*;
/**
* Define how the command method return result should
* be presented
*/
public enum CliView {
/**
* present the result using {@link act.cli.TableView}
*/
TABLE() {
@Override
@SuppressWarnings("unchecked")
public String render(Object result, PropertySpec.MetaInfo spec, ActContext context) {
if (null == result) {
return "no data";
}
spec = PropertySpec.MetaInfo.withCurrent(spec, context);
if (null == spec) {
spec = new PropertySpec.MetaInfo();
}
if (!(context instanceof CliContext)) {
throw E.unsupport("TableView support in CliContext only. You context: ", context.getClass());
}
CliContext cliContext = (CliContext) context;
List dataList = toList(result);
int pageSize = context instanceof CliOverHttpContext ? dataList.size() : context.config().cliTablePageSize();
if (dataList.size() > pageSize) {
TableCursor cursor = new TableCursor(dataList, pageSize, spec);
cliContext.session().cursor(cursor);
cursor.output(cliContext);
return "";
}
Class<?> componentType;
if (dataList.isEmpty()) {
return "no data";
}
componentType = Object.class;
for (Object o : dataList) {
if (null != o) {
componentType = o.getClass();
break;
}
}
DataPropertyRepository repo = context.app().service(DataPropertyRepository.class);
List<String> outputFields = repo.outputFields(spec, componentType, context);
if (outputFields.isEmpty()) {
outputFields = C.list("this as Item");
}
String tableString = cliContext.getTable(new CollectionASCIITableAware(dataList, outputFields, spec.labels(outputFields, context)));
int itemsFound = dataList.size();
CliCursor cursor = cliContext.session().cursor();
String appendix = "";
if (null != cursor) {
itemsFound = cursor.records();
appendix = cursor.hasNext() ? "\nType \"it\" for more" : "";
}
return S.concat(tableString, "Items found: ", S.string(itemsFound), appendix);
}
},
/**
* Present data in a Tree structure.
* <p>
* Note the {@code result} parameter must be a root {@link act.cli.tree.TreeNode node} of the tree,
* otherwise the data will be presented in
* </p>
* <ul>
* <li>{@link #TABLE Table view} if the result is an {@link Iterable}, or</li>
* <li>{@link #JSON JSON view} otherwise</li>
* </ul>
*/
TREE() {
@Override
public String render(Object result, PropertySpec.MetaInfo spec, ActContext context) {
if (result instanceof TreeNode) {
return toTreeString((TreeNode) result);
} else if (result instanceof Iterable) {
return TABLE.render(result, spec, context);
} else if (null != spec) {
return JSON.render(result, spec, context);
} else {
return S.string(result);
}
}
private String toTreeString(TreeNode result) {
StringBuilder sb = S.newBuilder();
buildTree(sb, result, "", true);
return sb.toString();
}
private void buildTree(StringBuilder sb, TreeNode node, String prefix, boolean isTrail) {
StringBuilder sb0 = S.newBuilder().append(prefix).append(isTrail ? "└── " : "├── ").append(node.label()).append("\n");
sb.append(sb0);
List<TreeNode> children = node.children();
int sz = children.size();
if (sz == 0) {
return;
}
final String subPrefix = S.newBuilder().append(prefix).append(isTrail ? " " : "│ ").toString();
for (int i = 0; i < sz - 1; ++i) {
TreeNode child = children.get(i);
buildTree(sb, child, subPrefix, false);
}
buildTree(sb, children.get(sz - 1), subPrefix, true);
}
},
/**
* Present the result using {@link act.cli.XmlView}
*/
XML() {
@Override
public String render(Object result, PropertySpec.MetaInfo spec, ActContext context) {
throw E.unsupport();
}
},
/**
* Present the result using {@link act.cli.JsonView}
*/
JSON() {
@Override
public String render(Object result, PropertySpec.MetaInfo spec, ActContext context) {
return render(result, spec, context, context instanceof CliContext);
}
public String render(Object result, PropertySpec.MetaInfo spec, ActContext context, boolean format) {
String json;
FastJsonPropertyPreFilter propertyFilter;
spec = PropertySpec.MetaInfo.withCurrent(spec, context);
if (null == spec) {
propertyFilter = null;
} else {
propertyFilter = new FastJsonPropertyPreFilter();
List<String> outputs = spec.outputFields(context);
Set<String> excluded = spec.excludedFields(context);
if (excluded.isEmpty()) {
if (outputs.isEmpty()) {
propertyFilter = null; // no filter defined actually
} else {
// output fields only applied when excluded fields not presented
propertyFilter.addIncludes(outputs);
if (FastJsonPropertyPreFilter.hasPattern(outputs)) {
// TODO: handle the case when result is an Iterable
propertyFilter.setFullPaths(context.app().service(DataPropertyRepository.class).propertyListOf(result.getClass()));
}
}
} else {
propertyFilter.addExcludes(excluded);
if (FastJsonPropertyPreFilter.hasPattern(excluded)) {
// TODO: handle the case when result is an Iterable
propertyFilter.setFullPaths(context.app().service(DataPropertyRepository.class).propertyListOf(result.getClass()));
}
}
}
List<SerializerFeature> featureList = C.newList();
if (format) {
featureList.add(SerializerFeature.PrettyFormat);
}
if (null == propertyFilter) {
Boolean b = DisableFastJsonCircularReferenceDetect.option.get();
if (null != b && b) {
featureList.add(SerializerFeature.DisableCircularReferenceDetect);
}
SerializerFeature[] featureArray = new SerializerFeature[featureList.size()];
featureArray = featureList.toArray(featureArray);
if (format) {
json = com.alibaba.fastjson.JSON.toJSONString(result, featureArray);
} else {
json = com.alibaba.fastjson.JSON.toJSONString(result);
}
} else {
// Note: we can't check DisableFastJsonCircularReferenceDetect here because if
// that option is set, then FastJson will skip the JsonSerializer.context setting
// and there is property filter mechanism will break
MappedFastJsonNameFilter nameFilter = new MappedFastJsonNameFilter(spec.labelMapping(context));
SerializerFeature[] featureArray = new SerializerFeature[featureList.size()];
featureArray = featureList.toArray(featureArray);
if (nameFilter.isEmpty()) {
json = com.alibaba.fastjson.JSON.toJSONString(result, propertyFilter, featureArray);
} else {
SerializeFilter[] filters = new SerializeFilter[2];
filters[0] = nameFilter;
filters[1] = propertyFilter;
json = com.alibaba.fastjson.JSON.toJSONString(result, filters, featureArray);
}
}
return json;
}
},
/**
* Present the result using {@link Object#toString()}
*/
TO_STRING() {
@Override
public String render(Object result, PropertySpec.MetaInfo filter, ActContext context) {
if (result instanceof Iterable) {
return TABLE.render(result, filter, context);
} else if (result instanceof TreeNode) {
return TREE.render(result, filter, context);
} else if (null != filter) {
return JSON.render(result, filter, context);
} else {
return S.string(result);
}
}
},
CSV() {
@Override
public String render(Object result, PropertySpec.MetaInfo spec, ActContext context) {
if (null == result) {
return "no data";
}
List dataList;
Class<?> componentType;
if (result instanceof Iterable) {
dataList = C.list((Iterable) result);
} else if (result instanceof Iterator) {
dataList = C.list((Iterator) result);
} else if (result instanceof Enumeration) {
dataList = C.list((Enumeration) result);
} else {
dataList = C.list(result);
}
if (dataList.isEmpty()) {
return "no data";
}
componentType = dataList.get(0).getClass();
DataPropertyRepository repo = context.app().service(DataPropertyRepository.class);
spec = PropertySpec.MetaInfo.withCurrent(spec, context);
if (null == spec) {
spec = new PropertySpec.MetaInfo();
spec.onValue("-not_exists");
}
List<String> outputFields = repo.outputFields(spec, componentType, context);
S.Buffer sb = S.buffer();
buildHeaderLine(sb, outputFields, spec.labelMapping());
for (Object entity : dataList) {
sb.append($.OS.lineSeparator());
buildDataLine(sb, entity, outputFields);
}
return sb.toString();
}
private void buildDataLine(S.Buffer sb, Object data, List<String> outputFields) {
Iterator<String> itr = outputFields.iterator();
String prop = itr.next();
sb.append(getProperty(data, prop));
while (itr.hasNext()) {
sb.append(",").append(getProperty(data, itr.next()));
}
}
private String getProperty(Object data, String prop) {
if ("this".equals(prop)) {
return (escape(data));
} else {
return escape($.getProperty(data, prop));
}
}
private void buildHeaderLine(S.Buffer sb, List<String> outputFields, Map<String, String> labels) {
if (null == labels) {
labels = C.newMap();
}
Iterator<String> itr = outputFields.iterator();
String label = label(itr.next(), labels);
sb.append(label);
while (itr.hasNext()) {
sb.append(",").append(escape(label(itr.next(), labels)));
}
}
private String label(String key, Map<String, String> labels) {
String s = labels.get(key);
return null == s ? key : s;
}
private String escape(Object o) {
return Escape.CSV.apply(o).toString();
}
};
public String render(Object result, PropertySpec.MetaInfo spec, ActContext context) {
throw E.unsupport();
}
public void print(Object result, PropertySpec.MetaInfo spec, CliContext context) {
context.println(render(result, spec, context));
}
protected List toList(Object result) {
List dataList;
if (result instanceof Iterable) {
dataList = C.list((Iterable) result);
} else if (result instanceof Iterator) {
dataList = C.list((Iterator) result);
} else if (result instanceof Enumeration) {
dataList = C.list((Enumeration) result);
} else {
dataList = C.listOf(result);
}
return dataList;
}
}