Skip to content

Commit

Permalink
LINGO-556: Emit correct primitive types in JSON descriptors (Boolean,…
Browse files Browse the repository at this point in the history
… numbers) and render them appropriately
  • Loading branch information
dweiss committed Jun 10, 2020
1 parent 4b73654 commit 8bfc7b8
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 39 deletions.
12 changes: 10 additions & 2 deletions doc/src/js/attributes-details.js
Expand Up @@ -28,6 +28,14 @@ const depthFirstAttributes = descriptor => {
return collect(descriptor, []);
};

const attrValueToString = value => {
if (Array.isArray(value)) {
return "[]";
} else {
return value;
}
};

const descriptionText = attribute => {
const javadoc = attribute.javadoc;
if (javadoc.text) {
Expand Down Expand Up @@ -76,8 +84,8 @@ const attributeDetailsHtml = (attribute) => {
<dt>Type</dt>
<dd>${attribute.type}</dd>
<dt>Default</dt>
<dd>${attribute.value}</dd>
<dd>${constraintsHtml}</dd>
<dd>${attrValueToString(attribute.value)}</dd>
${constraintsHtml}
<dt>Path</dt>
<dd>${attribute.pathRest}</dd>
<dt>Java snippet</dt>
Expand Down
21 changes: 16 additions & 5 deletions doc/src/js/attributes-outline.js
Expand Up @@ -30,7 +30,7 @@ const attributeValue = (attribute, descriptor) => {
.join("")
};
} else {
return descriptor.value || "null";
return descriptor.value;
}
};

Expand Down Expand Up @@ -71,15 +71,26 @@ const attributesAndTypeHtml = (attributes, type) => {
const token = (type, content) => `<span class="token ${type}">${content}</span>`;
const punctuation = char => token("punctuation", char);
const value = v => {
const isNumber = Number.isFinite(parseFloat(v));
const isNumber = Number.isFinite(v);
const isNull = v === "null" || v === null;
const isBoolean = typeof v === "boolean";
const isNested = typeof v === "object" && v !== null && v.value !== undefined;

if (isNested) {
if (Array.isArray(v)) {
if (v.length !== 0) {
throw "Non-empty arrays not implemented."
}
return punctuation("[") + punctuation("]");
} else if (isNested) {
return wrapInBrackets(v.value);
} else if (isNull) {
return token("keyword", "null");
} else if (isNumber) {
return token("number", v);
} else if (isBoolean) {
return token("keyword", v);
} else {
return token(isNumber ? "number" : isNull ? "keyword" : "string",
isNumber ? v : isNull ? "null" : `"${v}"`);
return token("string", `"${v}"`);
}
};
const wrapInBrackets = string => punctuation("{") + string + punctuation("}");
Expand Down
Expand Up @@ -38,7 +38,10 @@ public class AttrInfo {

@JsonProperty public String description;
@JsonProperty public String type;
@JsonProperty public Object value;

@JsonInclude(Include.ALWAYS)
@JsonProperty
public Object value;

@JsonProperty public List<String> constraints;

Expand Down
Expand Up @@ -10,7 +10,6 @@
*/
package org.carrot2.infra.docattrs;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -30,7 +29,6 @@
import org.carrot2.attrs.AttrVisitor;
import org.carrot2.attrs.ClassNameMapper;
import org.carrot2.attrs.Constraint;
import org.carrot2.math.mahout.Arrays;

class AttrInfoCollector implements AttrVisitor {
private final ClassNameMapper aliasMapper;
Expand All @@ -43,78 +41,65 @@ public AttrInfoCollector(Map<String, AttrInfo> attributes, ClassNameMapper alias

@Override
public void visit(String key, AttrBoolean attr) {
attrInfo(key, attr, () -> "Boolean");
attrInfo(key, attr, () -> "Boolean", attr.get());
}

@Override
public void visit(String key, AttrInteger attr) {
attrInfo(key, attr, () -> "Integer");
attrInfo(key, attr, () -> "Integer", attr.get());
}

@Override
public void visit(String key, AttrDouble attr) {
attrInfo(key, attr, () -> "Double");
attrInfo(key, attr, () -> "Double", attr.get());
}

@Override
public void visit(String key, AttrString attr) {
attrInfo(key, attr, () -> "String");
attrInfo(key, attr, () -> "String", attr.get());
}

@Override
public <T extends Enum<T>> void visit(String key, AttrEnum<T> attr) {
attrInfo(key, attr, () -> attr.enumClass().getName());
attrInfo(key, attr, () -> attr.enumClass().getName(), Objects.toString(attr.get(), null));
}

@Override
public void visit(String key, AttrStringArray attr) {
attrInfo(key, attr, () -> "String[]");
attrInfo(key, attr, () -> "String[]", attr.get());
}

@Override
public <T extends AcceptingVisitor> void visit(String key, AttrObject<T> attr) {
attrInfo(key, attr, () -> attr.getInterfaceClass().getName());
attrInfo(
key,
attr,
() -> attr.getInterfaceClass().getName(),
Optional.ofNullable(attr.get()).map(aliasMapper::toName).orElse(null));
}

@Override
public <T extends AcceptingVisitor> void visit(String key, AttrObjectArray<T> attr) {
attrInfo(key, attr, () -> attr.getInterfaceClass().getName() + "[]");
if (attr.get() != null && !attr.get().isEmpty()) {
throw new RuntimeException(
"Don't know how to emit value for non-empty array attribute: " + key);
}
attrInfo(key, attr, () -> attr.getInterfaceClass().getName() + "[]", attr.get());
}

private <T> AttrInfo attrInfo(String key, Attr<T> attr, Supplier<String> type) {
private <T> AttrInfo attrInfo(String key, Attr<T> attr, Supplier<String> type, Object value) {
AttrInfo info = new AttrInfo();
info.attr = attr;
info.description = attr.getDescription();
info.type = type.get();
info.value = value;

List<Constraint<? super T>> constraints = attr.getConstraints();
if (!constraints.isEmpty()) {
info.constraints =
constraints.stream().map(Constraint::description).collect(Collectors.toList());
}

Optional<Object> value = Optional.ofNullable(attr.get());
if (attr instanceof AttrObject<?>) {
value = value.map(aliasMapper::toName);
} else if (attr instanceof AttrStringArray) {
// no change, just emit an array of strings.
} else if (attr instanceof AttrObjectArray<?>) {
if (value.isPresent()) {
if (!((List<?>) value.get()).isEmpty()) {
throw new RuntimeException(
"Don't know how to generate a descriptor for non-empty attribute: "
+ attr.getDescription());
}
}
} else {
value = value.map(Objects::toString);
}
info.value = value.orElse(null);

attrs.put(key, info);
return info;
}
Expand Down
Expand Up @@ -78,7 +78,7 @@ public class WriteDescriptorsCommand extends Command<ExitCode> {

@Override
public ExitCode run() {
BiConsumer<Class<? extends ClusteringAlgorithm>, ClassInfo> emitter = getOutputConsumer();
BiConsumer<Class<? extends AcceptingVisitor>, ClassInfo> emitter = getOutputConsumer();

// Collect attribute-holding class universe.
Map<String, Object> aliasedTypes = new TreeMap<>();
Expand Down Expand Up @@ -284,7 +284,7 @@ private List<ClusteringAlgorithm> collectAlgorithms() {
}

@SuppressForbidden("Legitimate sysout")
private BiConsumer<Class<? extends ClusteringAlgorithm>, ClassInfo> getOutputConsumer() {
private BiConsumer<Class<? extends AcceptingVisitor>, ClassInfo> getOutputConsumer() {
DefaultPrettyPrinter pp = new DefaultPrettyPrinter();
pp.indentArraysWith(new DefaultIndenter(" ", DefaultIndenter.SYS_LF));

Expand Down
@@ -0,0 +1,122 @@
/*
* Carrot2 project.
*
* Copyright (C) 2002-2020, Dawid Weiss, Stanisław Osiński.
* All rights reserved.
*
* Refer to the full license file "carrot2.LICENSE"
* in the root folder of the repository checkout or at:
* https://www.carrot2.org/carrot2.LICENSE
*/
package org.carrot2.infra.docattrs;

import java.time.DayOfWeek;
import org.assertj.core.api.Assertions;
import org.carrot2.attrs.AttrBoolean;
import org.carrot2.attrs.AttrComposite;
import org.carrot2.attrs.AttrDouble;
import org.carrot2.attrs.AttrEnum;
import org.carrot2.attrs.AttrInteger;
import org.carrot2.attrs.AttrObject;
import org.carrot2.attrs.AttrString;
import org.carrot2.attrs.JvmNameMapper;
import org.junit.Test;

public class TestClassInfoCollector {
@Test
public void testBooleanAttr() {
@SuppressWarnings("unused")
class Clazz extends AttrComposite {
AttrBoolean attrTrue =
attributes.register("attrTrue", AttrBoolean.builder().defaultValue(true));
AttrBoolean attrFalse =
attributes.register("attrFalse", AttrBoolean.builder().defaultValue(false));
AttrBoolean attrNull =
attributes.register("attrNull", AttrBoolean.builder().defaultValue(null));
}

ClassInfoCollector collector = new ClassInfoCollector(JvmNameMapper.INSTANCE);
ClassInfo classInfo = collector.collect(new Clazz());
Assertions.assertThat(classInfo.attributes)
.hasEntrySatisfying("attrTrue", info -> Assertions.assertThat(info.value).isEqualTo(true))
.hasEntrySatisfying("attrFalse", info -> Assertions.assertThat(info.value).isEqualTo(false))
.hasEntrySatisfying("attrNull", info -> Assertions.assertThat(info.value).isNull());
}

@Test
public void testNumericAttr() {
@SuppressWarnings("unused")
class Clazz extends AttrComposite {
AttrInteger attrInteger =
attributes.register("attrInteger", AttrInteger.builder().defaultValue(42));
AttrDouble attrDouble =
attributes.register("attrDouble", AttrDouble.builder().defaultValue(42.5));
}

ClassInfoCollector collector = new ClassInfoCollector(JvmNameMapper.INSTANCE);
ClassInfo classInfo = collector.collect(new Clazz());
Assertions.assertThat(classInfo.attributes)
.hasEntrySatisfying("attrInteger", info -> Assertions.assertThat(info.value).isEqualTo(42))
.hasEntrySatisfying(
"attrDouble", info -> Assertions.assertThat(info.value).isEqualTo(42.5));
}

@Test
public void testEnumAttr() {
@SuppressWarnings("unused")
class Clazz extends AttrComposite {
AttrEnum<DayOfWeek> attrEnum =
attributes.register(
"attrEnum", AttrEnum.builder(DayOfWeek.class).defaultValue(DayOfWeek.FRIDAY));
AttrEnum<DayOfWeek> attrEnumNull =
attributes.register("attrEnumNull", AttrEnum.builder(DayOfWeek.class).defaultValue(null));
}

ClassInfoCollector collector = new ClassInfoCollector(JvmNameMapper.INSTANCE);
ClassInfo classInfo = collector.collect(new Clazz());
Assertions.assertThat(classInfo.attributes)
.hasEntrySatisfying(
"attrEnum",
info -> Assertions.assertThat(info.value).isEqualTo(DayOfWeek.FRIDAY.toString()))
.hasEntrySatisfying("attrEnumNull", info -> Assertions.assertThat(info.value).isNull());
}

@Test
public void testStringAttr() {
@SuppressWarnings("unused")
class Clazz extends AttrComposite {
AttrString attr = attributes.register("attr", AttrString.builder().defaultValue("foo"));
AttrString attrNull =
attributes.register("attrNull", AttrString.builder().defaultValue(null));
}

ClassInfoCollector collector = new ClassInfoCollector(JvmNameMapper.INSTANCE);
ClassInfo classInfo = collector.collect(new Clazz());
Assertions.assertThat(classInfo.attributes)
.hasEntrySatisfying("attr", info -> Assertions.assertThat(info.value).isEqualTo("foo"))
.hasEntrySatisfying("attrNull", info -> Assertions.assertThat(info.value).isNull());
}

@Test
public void testObjectAttr() {
class Foo extends AttrComposite {}

@SuppressWarnings("unused")
class Clazz extends AttrComposite {
AttrObject<Foo> attr =
attributes.register("attr", AttrObject.builder(Foo.class).defaultValue(Foo::new));
AttrObject<Foo> attrNull =
attributes.register("attrNull", AttrObject.builder(Foo.class).defaultValue(() -> null));
}

ClassInfoCollector collector = new ClassInfoCollector(JvmNameMapper.INSTANCE);
ClassInfo classInfo = collector.collect(new Clazz());
Assertions.assertThat(classInfo.attributes)
.hasEntrySatisfying(
"attr",
info ->
Assertions.assertThat(info.value)
.isEqualTo(JvmNameMapper.INSTANCE.toName(new Foo())))
.hasEntrySatisfying("attrNull", info -> Assertions.assertThat(info.value).isNull());
}
}

0 comments on commit 8bfc7b8

Please sign in to comment.