diff --git a/src/main/java/org/comroid/annotations/Category.java b/src/main/java/org/comroid/annotations/Category.java new file mode 100644 index 00000000..46269132 --- /dev/null +++ b/src/main/java/org/comroid/annotations/Category.java @@ -0,0 +1,14 @@ +package org.comroid.annotations; + +import org.comroid.annotations.internal.Inherit; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@Inherit(Inherit.Type.FromBoth) +public @interface Category { + String value() default ""; + + Description[] desc() default {}; +} diff --git a/src/main/java/org/comroid/annotations/Default.java b/src/main/java/org/comroid/annotations/Default.java new file mode 100644 index 00000000..a4aeee02 --- /dev/null +++ b/src/main/java/org/comroid/annotations/Default.java @@ -0,0 +1,15 @@ +package org.comroid.annotations; + +import org.intellij.lang.annotations.Language; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +public @interface Default { + @Language("JShellLanguage") + String value() default "null"; +} diff --git a/src/main/java/org/comroid/annotations/Description.java b/src/main/java/org/comroid/annotations/Description.java new file mode 100644 index 00000000..fe211ecc --- /dev/null +++ b/src/main/java/org/comroid/annotations/Description.java @@ -0,0 +1,12 @@ +package org.comroid.annotations; + +import org.comroid.annotations.internal.Inherit; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@Inherit(Inherit.Type.FromSupertype) +public @interface Description { + String[] value() default {}; +} diff --git a/src/main/java/org/comroid/annotations/Order.java b/src/main/java/org/comroid/annotations/Order.java new file mode 100644 index 00000000..8b201fa9 --- /dev/null +++ b/src/main/java/org/comroid/annotations/Order.java @@ -0,0 +1,12 @@ +package org.comroid.annotations; + +import org.comroid.annotations.internal.Inherit; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@Inherit(Inherit.Type.FromSupertype) +public @interface Order { + int value(); +} diff --git a/src/main/java/org/comroid/annotations/internal/Annotations.java b/src/main/java/org/comroid/annotations/internal/Annotations.java index ba420a93..1df27e52 100644 --- a/src/main/java/org/comroid/annotations/internal/Annotations.java +++ b/src/main/java/org/comroid/annotations/internal/Annotations.java @@ -1,14 +1,18 @@ package org.comroid.annotations.internal; +import jdk.jshell.JShell; +import jdk.jshell.JShellException; +import jdk.jshell.SnippetEvent; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.SneakyThrows; import lombok.Value; import lombok.experimental.UtilityClass; import lombok.extern.java.Log; -import org.comroid.annotations.Alias; -import org.comroid.annotations.Convert; -import org.comroid.annotations.Ignore; +import org.comroid.annotations.*; +import org.comroid.api.Polyfill; import org.comroid.api.data.seri.DataStructure; +import org.comroid.api.data.seri.StandardValueType; import org.comroid.api.func.ext.Wrap; import org.comroid.api.info.Constraint; import org.comroid.api.java.ReflectionHelper; @@ -20,6 +24,7 @@ import java.lang.annotation.ElementType; import java.lang.reflect.*; import java.util.*; +import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -34,6 +39,12 @@ @ApiStatus.Internal public class Annotations { public static final Class[] SystemFilters = new Class[]{Object.class, Class.class, Annotation.class}; + public static final Comparator OrderComparator = Comparator.comparingInt(it -> + findAnnotations(Order.class, it) + .findAny() + .map(Result::getAnnotation) + .map(Order::value) + .orElse(0)); @ApiStatus.Experimental @Convert(identifyVia = "annotationType") @@ -48,6 +59,41 @@ public Set aliases(@NotNull AnnotatedElement of) { .collect(Collectors.toUnmodifiableSet()); } + public Stream description(@NotNull AnnotatedElement of) { + return findAnnotations(Description.class, of) + .flatMap(it -> stream(it.annotation.value())); + } + + public Wrap> category(@NotNull AnnotatedElement of) { + return Wrap.ofStream(findAnnotations(Category.class, of)); + } + + public @Nullable R defaultValue(@NotNull AnnotatedElement of) { + final var silent = new Object(){ + @SneakyThrows + public void throwIfExcPresent(SnippetEvent e) { + var exc = e.exception(); + if (exc != null) + throw exc; + } + }; + try (final var jShell = JShell.create()) { + return findAnnotations(Default.class, of) + .map(Result::getAnnotation) + .flatMap(expr -> jShell.eval(expr.value()).stream()) + .peek(silent::throwIfExcPresent) + .map(SnippetEvent::value) + .filter(Objects::nonNull) + .findAny() + .map(StandardValueType::findGoodType) + .map(Polyfill::uncheckedCast) + .orElse(null); + } catch (Throwable t) { + log.log(Level.WARNING, "Failed to evaluate default expression of " + of, t); + return null; + } + } + public Optional ignore(@NotNull AnnotatedElement it) { return ignore(it, null); } @@ -112,6 +158,8 @@ public Stream> findAnnotations(final Class t break; case FromSupertype, FromBoth: sources = sources.collect(append(findAncestor(member, type).stream())); + if (inherit != Inherit.Type.FromBoth) + break; case FromParent: sources = sources.collect(append(decl)); break; diff --git a/src/main/java/org/comroid/api/data/seri/DataNode.java b/src/main/java/org/comroid/api/data/seri/DataNode.java index 260aa456..d7c84f46 100644 --- a/src/main/java/org/comroid/api/data/seri/DataNode.java +++ b/src/main/java/org/comroid/api/data/seri/DataNode.java @@ -215,7 +215,7 @@ static Stream properties(final java.lang.Object it) { if (it instanceof DataNode.Base) return ((DataNode) it).properties(); return DataStructure.of(it.getClass(), java.lang.Object.class) - .getProperties().values().stream() + .getDeclaredProperties().values().stream() .map(Polyfill::.Property>uncheckedCast) .map(prop -> new Entry(prop.getName(), of(prop.getFrom(it)))); } @@ -309,7 +309,12 @@ public Stream properties() { @AllArgsConstructor @Ignore(Convertible.class) class Value extends Base implements ValueBox { - public static final DataNode NULL = new Value<>(null); + public static final DataNode NULL = new Value<>(null){ + @Override + public ValueType getHeldType() { + return VOID; + } + }; protected @Nullable T value; @Override diff --git a/src/main/java/org/comroid/api/data/seri/DataStructure.java b/src/main/java/org/comroid/api/data/seri/DataStructure.java index 4ff9325a..bda03ade 100644 --- a/src/main/java/org/comroid/api/data/seri/DataStructure.java +++ b/src/main/java/org/comroid/api/data/seri/DataStructure.java @@ -14,6 +14,7 @@ import org.comroid.api.func.ext.Wrap; import org.comroid.api.info.Log; import org.comroid.api.java.ReflectionHelper; +import org.comroid.api.text.Capitalization; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,10 +43,27 @@ public class DataStructure implements Named { @NotNull Class type; @NotNull @ToString.Exclude + List> parents = new ArrayList<>(); + @NotNull + @ToString.Exclude List constructors = new ArrayList<>(); @NotNull @ToString.Exclude - Map> properties = new ConcurrentHashMap<>(); + Map> declaredProperties = new ConcurrentHashMap<>(); + + public Set.Property> getProperties() { + var set = new HashSet.Property>(declaredProperties.values()); + parents.stream().flatMap(dataStructure -> dataStructure.getProperties().stream()) + .filter(prop -> set.stream().map(Member::getName).noneMatch(prop.name::equals)) + .forEach(set::add); + return set; + } + + public List.Property> getOrderedProperties() { + var list = new ArrayList<>(getProperties()); + list.sort(OrderComparator); + return list; + } @Override public String getName() { @@ -61,7 +79,38 @@ public Wrap> getProperty(java.lang.reflect.Member member) { } public Wrap> getProperty(String name) { - return Wrap.of(properties.getOrDefault(name, null)).castRef(); + return Wrap.ofStream(getProperties().stream() + .filter(prop -> Stream.concat(Stream.of(prop.name), prop.aliases.stream()) + .anyMatch(alias -> alias.equals(name)))) + .castRef(); + } + + public Set> update(Map data, T target) { + final var affected = new HashSet>(); + + for (final var prop : declaredProperties.values()) { + final var type = prop.type; + if (!(type instanceof StandardValueType)) { + log.fine("Skipping auto-update for " + prop + " because its not a standard type (" + type + ")"); + continue; + } + + final var name = prop.getName(); + if (!data.containsKey(name)) + continue; + if (!prop.canSet()) { + if (data.containsKey(name)) + log.warning("Data had value for " + prop + "; but the property is not settable"); + continue; + } + + var value = data.get(name); + var parse = type.parse(value); + prop.setFor(target, uncheckedCast(parse)); + affected.add(prop); + } + + return Collections.unmodifiableSet(affected); } public static DataStructure of(@NotNull Class target) { @@ -69,22 +118,18 @@ public static DataStructure of(@NotNull Class target) { } @lombok.Builder - public static DataStructure of(@NotNull Class target, @NotNull Class above) { - //return uncheckedCast($cache.touch(new Key(target, above))); - return uncheckedCast($cache.computeIfAbsent(new Key(target, above), DataStructure::create)); - } + public static DataStructure of(final @NotNull Class target, final @NotNull Class above) { + final var key = new Key<>(target, above); + if ($cache.containsKey(key)) + return uncheckedCast($cache.get(key)); - private static DataStructure create(Key key) { - final var target = key.type; final var struct = new DataStructure(target); - class Helper { + + var helper = new Object() { Stream streamRelevantMembers(Class decl) { return Stream.of(decl).flatMap(Streams.multiply( - c -> Stream.concat( - Arrays.stream(c.getFields()), - Arrays.stream(c.getDeclaredFields()) - .filter(fld -> !Modifier.isPublic(fld.getModifiers()))), - c -> Arrays.stream(c.getMethods()), + c -> Arrays.stream(c.getDeclaredFields()), + c -> Arrays.stream(c.getDeclaredMethods()), c -> Arrays.stream(c.getConstructors()))) .map(Polyfill::uncheckedCast); } @@ -103,7 +148,9 @@ boolean filterIgnored(AnnotatedElement member) { } boolean filterAbove(AnnotatedElement member) { - return !(member instanceof AccessibleObject obj) || !declaringClass(obj).isAssignableFrom(key.above); + if (member instanceof Class cls) return !cls.isAssignableFrom(above); + if (!(member instanceof AccessibleObject obj)) return true; + return !declaringClass(obj).isAssignableFrom(above); } boolean filterPropertyModifiers(java.lang.reflect.Member member) { @@ -114,7 +161,7 @@ boolean filterPropertyModifiers(java.lang.reflect.Member member) { boolean filterConstructorModifiers(java.lang.reflect.Member member) { final var mod = member.getModifiers(); return Map., IntPredicate>of( - Method.class, bit -> ((Method) member).getReturnType().equals(target), + Method.class, bit -> ((Method) member).getReturnType().equals(target) && Modifier.isStatic(bit), DataStructure.Constructor.class, bit -> true) .entrySet().stream() .anyMatch(e -> !e.getKey().isInstance(member) || e.getValue().test(mod)); @@ -132,6 +179,7 @@ else if (member instanceof Method mtd) DataStructure.Property

convertProperty(R member) { String name = member.getName(); + P defaultValue = Annotations.defaultValue(member); ValueType

type = null; Invocable

getter = null; Invocable setter = null; @@ -166,9 +214,9 @@ && checkAccess(candidate) } if (type == null) throw new AssertionError("Could not initialize property adapter for " + member); - DataStructure.Property

prop = uncheckedCast(struct.new Property<>(name, member, target, type, getter, setter)); - setAnnotations(member,prop); - setAliases(member,prop); + DataStructure.Property

prop = uncheckedCast(struct.new Property<>(name, member, target, type, defaultValue, getter, setter)); + setAnnotations(member, prop); + setAliases(member, prop); return prop; } @@ -194,8 +242,8 @@ DataStructure.Constru if (func == null) throw new AssertionError("Could not initialize construction adapter for " + member); DataStructure.Constructor ctor = struct.new Constructor(name, member, target, List.of(param), func); - setAnnotations(member,ctor); - setAliases(member,ctor); + setAnnotations(member, ctor); + setAliases(member, ctor); return ctor; } @@ -214,8 +262,8 @@ private void setAliases(AnnotatedElement source, DataStructure.Member member) { boolean checkAccess(AnnotatedElement member) { return member instanceof AccessibleObject obj && obj.trySetAccessible(); } - } - var helper = new Helper(); + }; + var count = helper.streamRelevantMembers(target) .filter(helper::filterDynamic) .filter(helper::filterSystem) @@ -226,15 +274,26 @@ boolean checkAccess(AnnotatedElement member) { .filter(helper::filterPropertyModifiers) .filter(helper::filterPropertyMembers) .map(helper::convertProperty) - .peek(prop -> Stream.concat(Stream.of(prop.getName()), prop.aliases.stream()) - .forEach(name -> struct.properties.put(name, uncheckedCast(prop)))), + .peek(member -> Stream.concat(Stream.of(member.getName()), member.aliases.stream()) + .map(Current.getProperties()::convert) + .forEach(name -> struct.declaredProperties.put(name, uncheckedCast(member)))), Stream.of(s) .filter(helper::filterConstructorModifiers) .filter(helper::filterConstructorMembers) .map(helper::convertConstructor) .peek(ctor -> struct.constructors.add(uncheckedCast(ctor))))) - //.peek(member -> System.out.println(member)) .count(); + + // init parents + $cache.put(key, struct); + Stream.of(target.getSuperclass()) + .collect(Streams.append(target.getInterfaces())) + .filter(Objects::nonNull) + .filter(helper::filterAbove) + .map(DataStructure::of) + .map(Polyfill::>uncheckedCast) + .forEach(struct.parents::add); + Log.at(Level.FINE, "Initialized %d members for %s".formatted(count, target.getCanonicalName())); return struct; } @@ -276,7 +335,7 @@ private Member(@NotNull String name, @NotNull AnnotatedElement context, @NotNull @Override public String getAlternateName() { - return aliases.stream().findAny().orElseGet(Named.super::getAlternateName); + return Title_Case.convert(getName()); } @Ignore @@ -314,6 +373,8 @@ public Stream> streamAnnotations(Class annot .orElse(null); } + public abstract String getCanonicalName(); + @Override public String toString() { return "%s (%s) %s.%s".formatted( @@ -355,6 +416,11 @@ public ValueType getHeldType() { return ValueType.of(declaringClass); } + @Override + public String getCanonicalName() { + return declaringClass.getCanonicalName() + ".ctor"; + } + @Override public String toString() { return super.toString(); @@ -364,6 +430,7 @@ public String toString() { @Value public class Property extends Member { @NotNull ValueType type; + @Nullable V defaultValue; @Nullable @ToString.Exclude @Getter(onMethod = @__(@JsonIgnore)) @@ -377,16 +444,19 @@ public Property(@NotNull String name, @NotNull AnnotatedElement context, @NotNull Class declaringClass, @NotNull ValueType type, + @Nullable V defaultValue, @Nullable Invocable getter, @Nullable Invocable setter) { super(name, context, declaringClass); this.type = type; + this.defaultValue = defaultValue; this.getter = getter; this.setter = setter; } public @Nullable V getFrom(T target) { - Constraint.notNull(getter, "getter"); + Constraint.notNull(getter, "getter").run(); + Constraint.notNull(target, "target").run(); return getter.invokeSilent(target); } @@ -408,6 +478,11 @@ public ValueType getHeldType() { return type; } + @Override + public String getCanonicalName() { + return declaringClass.getCanonicalName() + '.' + name; + } + @Override public String toString() { return super.toString() + " (" + (getter!=null?"get":"")+(setter!=null?" set":"")+')'; diff --git a/src/main/java/org/comroid/api/data/seri/StandardValueType.java b/src/main/java/org/comroid/api/data/seri/StandardValueType.java index 9509a12b..7b0b0fce 100644 --- a/src/main/java/org/comroid/api/data/seri/StandardValueType.java +++ b/src/main/java/org/comroid/api/data/seri/StandardValueType.java @@ -1,69 +1,75 @@ package org.comroid.api.data.seri; +import lombok.Value; import org.comroid.api.Polyfill; import org.comroid.api.func.ext.Wrap; +import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.ApiStatus.Experimental; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.UUID; import java.util.function.Function; -public final class StandardValueType implements ValueType { - public static final ValueType BOOLEAN; - public static final ValueType BYTE; - public static final ValueType CHARACTER; - public static final ValueType DOUBLE; - public static final ValueType FLOAT; - public static final ValueType INTEGER; - public static final ValueType LONG; - public static final ValueType SHORT; - public static final ValueType STRING; - public static final ValueType UUID; +@Value +public class StandardValueType implements ValueType { + public static final StandardValueType BOOLEAN; + public static final StandardValueType BYTE; + public static final StandardValueType CHARACTER; + public static final StandardValueType DOUBLE; + public static final StandardValueType FLOAT; + public static final StandardValueType INTEGER; + public static final StandardValueType LONG; + public static final StandardValueType SHORT; + public static final StandardValueType STRING; + public static final StandardValueType UUID; - public static final ValueType VOID; + public static final StandardValueType VOID; @Deprecated - public static final ValueType OBJECT; - public static final ValueType ARRAY; + public static final StandardValueType OBJECT; + public static final StandardValueType ARRAY; - public static final ValueType[] values; + public static final List> cache = new ArrayList<>(); static { - BOOLEAN = new StandardValueType<>(Boolean.class, "boolean", Boolean::parseBoolean); - BYTE = new StandardValueType<>(Byte.class, "byte", Byte::parseByte); - CHARACTER = new StandardValueType<>(Character.class, "char", str -> str.toCharArray()[0]); - DOUBLE = new StandardValueType<>(Double.class, "double", Double::parseDouble); - FLOAT = new StandardValueType<>(Float.class, "float", Float::parseFloat); - INTEGER = new StandardValueType<>(Integer.class, "int", Integer::parseInt); - LONG = new StandardValueType<>(Long.class, "long", Long::parseLong); - SHORT = new StandardValueType<>(Short.class, "short", Short::parseShort); - STRING = new StandardValueType<>(String.class, "String", Function.identity()); - UUID = new StandardValueType<>(UUID.class, "UUID", java.util.UUID::fromString); - VOID = new StandardValueType<>(void.class, "Void", it -> null); - OBJECT = new StandardValueType<>(Object.class, "Object", it -> it); - ARRAY = new StandardValueType<>(Object[].class, "Array", it -> new Object[]{it}); - - values = new ValueType[]{BYTE, CHARACTER, DOUBLE, FLOAT, INTEGER, LONG, SHORT, STRING, BOOLEAN, VOID, ARRAY}; + BOOLEAN = new StandardValueType<>(Boolean.class, "boolean", Boolean::parseBoolean, "checkbox"); + BYTE = new StandardValueType<>(Byte.class, "byte", Byte::parseByte, "number", "min='"+Byte.MIN_VALUE+"'", "max='"+Byte.MAX_VALUE+"'"); + CHARACTER = new StandardValueType<>(Character.class, "char", str -> str.toCharArray()[0], "text", "pattern='\\w'"); + DOUBLE = new StandardValueType<>(Double.class, "double", Double::parseDouble, "number", "min='"+Double.MIN_VALUE+"'", "max='"+Double.MAX_VALUE+"'"); + FLOAT = new StandardValueType<>(Float.class, "float", Float::parseFloat, "number", "min='"+Float.MIN_VALUE+"'", "max='"+Float.MAX_VALUE+"'"); + INTEGER = new StandardValueType<>(Integer.class, "int", Integer::parseInt, "number", "min='"+Integer.MIN_VALUE+"'", "max='"+Integer.MAX_VALUE+"'"); + LONG = new StandardValueType<>(Long.class, "long", Long::parseLong, "number", "min='"+Long.MIN_VALUE+"'", "max='"+Long.MAX_VALUE+"'"); + SHORT = new StandardValueType<>(Short.class, "short", Short::parseShort, "number", "min='"+Short.MIN_VALUE+"'", "max='"+Short.MAX_VALUE+"'"); + STRING = new StandardValueType<>(String.class, "String", Function.identity(), "text"); + UUID = new StandardValueType<>(UUID.class, "UUID", java.util.UUID::fromString, "text", "pattern='"+RegExpUtil.UUID4_PATTERN+"'"); + VOID = new StandardValueType<>(void.class, "Void", it -> null, "hidden"); + OBJECT = new StandardValueType<>(Object.class, "Object", it -> it, "hidden"); + ARRAY = new StandardValueType<>(Object[].class, "Array", it -> new Object[]{it}, "hidden"); } - private final Class type; - private final String name; - private final Function converter; - - @Override - public String getName() { - return name; - } + private StandardValueType( + Class targetClass, + String name, + Function converter, + @Language(value = "HTML", prefix = "") String htmlInputType, + @Language(value = "HTML", prefix = " getTargetClass() { - return type; + cache.add(this); } - public StandardValueType(Class type, String name, Function mapper) { - this.type = type; - this.name = name; - this.converter = mapper; - } + Class targetClass; + String name; + Function converter; + @Language(value = "HTML", prefix = "") + String htmlInputType; + @Language(value = "HTML", prefix = " ValueType typeOf(T value) { if (value == null) //noinspection unchecked return (ValueType) StandardValueType.VOID; - return Arrays.stream(values) + return cache.stream() .filter(it -> it.test(value)) .findAny() .map(Polyfill::>uncheckedCast) @@ -92,7 +100,7 @@ public static ValueType typeOf(T value) { } public static Wrap> forClass(Class cls) { - return Wrap.ofOptional(Arrays.stream(values) + return Wrap.ofOptional(cache.stream() .filter(it -> it.getTargetClass().isAssignableFrom(cls) || (cls.isPrimitive() && it.getName().equals(cls.getSimpleName()))) .findAny()); } diff --git a/src/main/java/org/comroid/api/data/seri/ValueType.java b/src/main/java/org/comroid/api/data/seri/ValueType.java index ba636e66..725730dd 100644 --- a/src/main/java/org/comroid/api/data/seri/ValueType.java +++ b/src/main/java/org/comroid/api/data/seri/ValueType.java @@ -5,10 +5,13 @@ import org.comroid.api.Polyfill; import org.comroid.api.func.ValuePointer; import org.comroid.api.attr.Named; +import org.intellij.lang.annotations.Language; +import org.jetbrains.annotations.Nullable; import javax.persistence.Transient; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Stream; public interface ValueType extends ValuePointer, Predicate, Named { static ValueType of(final Class type) { @@ -30,6 +33,10 @@ default boolean isNumeric() { return Number.class.isAssignableFrom(getTargetClass()); } + default boolean isStandard() { + return this instanceof StandardValueType && Stream.of(StandardValueType.OBJECT, StandardValueType.ARRAY).noneMatch(this::equals); + } + @Ignore @Transient @JsonIgnore @@ -53,5 +60,15 @@ default T convert(R value, ValueType toType) { return toType.parse(value.toString()); } + @Language(value = "HTML", prefix = "") + default String getHtmlInputType() { + return "text"; + } + + @Language(value = "HTML", prefix = " type) { return Optional.ofNullable(type.getCanonicalName()) - .map(name->name.substring(type.getPackageName().length() + 1)) + .map(name -> name.indexOf('.') == -1 ? name + : name.substring(type.getPackageName().length() + 1)) .orElseGet(type::getSimpleName); } diff --git a/src/main/java/org/comroid/api/text/Capitalization.java b/src/main/java/org/comroid/api/text/Capitalization.java index 12ad04e2..a052d85b 100644 --- a/src/main/java/org/comroid/api/text/Capitalization.java +++ b/src/main/java/org/comroid/api/text/Capitalization.java @@ -2,6 +2,8 @@ import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Value; import lombok.experimental.Delegate; import lombok.experimental.FieldDefaults; import lombok.extern.java.Log; @@ -44,6 +46,9 @@ public enum Capitalization implements Named, Comparator { Title_Case(State.Upper, State.Lower, State.Upper, ' '); // "Title Case" + public static final Context Default = Context.builder().build(); + public static final Context Current = Default; + State firstChar; State midWord; State recurringFirstChar; @@ -184,4 +189,15 @@ static State find(int c) { .orElse(Any); } } + + @Value + @Builder + public static class Context { + @lombok.Builder.Default Capitalization constants = UpperCamelCase; + @lombok.Builder.Default Capitalization types = UpperCamelCase; + @lombok.Builder.Default Capitalization properties = lowerCamelCase; + @lombok.Builder.Default Capitalization paths = lower_hyphen_case; + @lombok.Builder.Default Capitalization display = Title_Case; + @lombok.Builder.Default Capitalization version = lower_dot_case; + } } diff --git a/src/main/java/org/comroid/api/tree/Component.java b/src/main/java/org/comroid/api/tree/Component.java index d958a516..9e85955e 100644 --- a/src/main/java/org/comroid/api/tree/Component.java +++ b/src/main/java/org/comroid/api/tree/Component.java @@ -9,7 +9,6 @@ import org.comroid.api.attr.BitmaskAttribute; import org.comroid.api.attr.EnabledState; import org.comroid.api.attr.Named; -import org.comroid.api.func.util.Invocable; import org.comroid.api.func.ext.Wrap; import org.comroid.api.func.exc.ThrowingConsumer; import org.comroid.api.info.Log; @@ -139,16 +138,14 @@ static Set> dependencies(Class type) { return Cache.get("dependencies of " + type.getCanonicalName(), () -> { var struct = DataStructure.of(type); return Stream.concat( - struct.getProperties().values().stream() - //.flatMap(prop -> prop.annotations.stream()) - //.map(Polyfill::>uncheckedCast) - .flatMap(expandFlat(prop -> prop.streamAnnotations(Inject.class))) - .map(mapB(Annotations.Result::getAnnotation)) - .map(combine((prop, inject) -> new Dependency<>( - inject.value(), - uncheckedCast(prop.getType().getTargetClass()), - inject.required(), - uncheckedCast(prop)))), + struct.getProperties().stream() + .flatMap(prop -> prop.streamAnnotations(Inject.class) + .map(Annotations.Result::getAnnotation) + .map(inject -> new Dependency<>( + inject.value(), + uncheckedCast(prop.getType().getTargetClass()), + inject.required(), + uncheckedCast(prop)))), Annotations.findAnnotations(Requires.class, type) .flatMap(requires -> Arrays.stream(requires.getAnnotation().value()) .map(cls -> new Dependency<>("", cls, true, null)))) @@ -235,7 +232,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(name, type, required); + return Objects.hash(name, type, required, prop); } } diff --git a/src/main/java/org/comroid/api/tree/Container.java b/src/main/java/org/comroid/api/tree/Container.java index d62a90f0..21abbedc 100644 --- a/src/main/java/org/comroid/api/tree/Container.java +++ b/src/main/java/org/comroid/api/tree/Container.java @@ -2,6 +2,7 @@ import lombok.*; import lombok.experimental.FieldDefaults; +import org.comroid.annotations.Ignore; import org.comroid.api.Polyfill; import org.comroid.api.attr.EnabledState; import org.comroid.api.attr.Owned; @@ -56,10 +57,12 @@ private static Exception makeException(List errors) { } class Base implements Container, Reloadable { + @Ignore @Getter(onMethod = @__(@Ignore)) private final AtomicReference> closed = new AtomicReference<>(new CompletableFuture<>()); - @Getter + @Ignore @Getter(onMethod = @__(@Ignore)) final Set children; + @Ignore public boolean isClosed() { return closed.get().isDone(); } diff --git a/src/test/java/org/comroid/test/Dummy.java b/src/test/java/org/comroid/test/Dummy.java index f0fd8fd2..05ccf26e 100644 --- a/src/test/java/org/comroid/test/Dummy.java +++ b/src/test/java/org/comroid/test/Dummy.java @@ -49,7 +49,6 @@ public Apple(double hardness) { } @Override - @Ignore.Inherit(Alias.class) public double getPrice() { return 0.99; } diff --git a/src/test/java/org/comroid/test/api/DataStructureTest.java b/src/test/java/org/comroid/test/api/DataStructureTest.java index df27d5f9..6d19ce14 100644 --- a/src/test/java/org/comroid/test/api/DataStructureTest.java +++ b/src/test/java/org/comroid/test/api/DataStructureTest.java @@ -4,22 +4,20 @@ import org.comroid.api.data.seri.StandardValueType; import org.comroid.api.data.seri.ValueType; import org.comroid.api.func.util.Debug; -import org.comroid.api.info.Log; +import org.comroid.api.func.util.Invocable; import org.comroid.test.Dummy; import org.junit.Test; -import java.util.logging.Level; - import static org.junit.Assert.*; public class DataStructureTest { @Test public void testFruit() { var struct = DataStructure.of(Dummy.Fruit.class); - System.out.println(Debug.createObjectDump(struct)); + //System.out.println(Debug.createObjectDump(struct)); assertEquals("invalid fruit constructor count", 1, struct.getConstructors().size()); - assertFalse("invalid fruit property count", struct.getProperties().isEmpty()); + assertEquals("invalid fruit property count", 2, struct.getDeclaredProperties().size()); //price testProp(struct, StandardValueType.DOUBLE, "price", 1.99); @@ -28,10 +26,10 @@ public void testFruit() { @Test public void testApple() { var struct = DataStructure.of(Dummy.Apple.class); - System.out.println(Debug.createObjectDump(struct)); + //System.out.println(Debug.createObjectDump(struct)); assertEquals("invalid apple constructor count", 2, struct.getConstructors().size()); - assertTrue("invalid apple property count", 2 <= struct.getProperties().size()); + assertEquals("invalid apple property count", 2, struct.getDeclaredProperties().size()); // price testProp(struct, StandardValueType.DOUBLE, "price", 0.99); @@ -43,10 +41,10 @@ public void testApple() { @Test public void testBanana() { var struct = DataStructure.of(Dummy.Banana.class); - System.out.println(Debug.createObjectDump(struct)); + //System.out.println(Debug.createObjectDump(struct)); assertEquals("invalid banana constructor count", 1, struct.getConstructors().size()); - assertTrue("invalid banana property count", 3 <= struct.getProperties().size()); + assertEquals("invalid banana property count", 2, struct.getDeclaredProperties().size()); // price testProp(struct, StandardValueType.DOUBLE, "price", 1.99); @@ -57,8 +55,9 @@ public void testBanana() { } private void testProp(DataStructure struct, ValueType type, String key, Object expectedValue) { - assertTrue(key+" property: missing", struct.getProperties().containsKey(key)); - var prop = struct.getProperties().get(key); + assertTrue(key+" property: missing", struct.getProperty(key).isNonNull()); + var prop = struct.getProperty(key).assertion(); + assertNotNull(prop); var dummy = struct.getConstructors() .stream() .filter(ctor->ctor.getArgs().isEmpty()) @@ -71,7 +70,7 @@ private void testProp(DataStructure struct, ValueType type, String key, Ob assertEquals(key+" property: wrong type", type, prop.getType()); // accessors - var accessor = prop.getGetter(); + Invocable accessor = prop.getGetter(); assertNotNull(key+" property: getter missing", accessor); assertEquals(key+" property: getter unusable", expectedValue, accessor.invokeSilent(dummy)); if ("price".equals(prop.getName())) {