Initial commit

This commit is contained in:
Peter Niederwieser
2016-01-19 14:51:19 +01:00
committed by Dan Chao
commit ecad035dca
2972 changed files with 211653 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import java.lang.reflect.Type;
import java.util.Map;
import org.pkl.config.java.mapper.ValueMapper;
import org.pkl.core.Composite;
abstract class AbstractConfig implements Config {
protected final String qualifiedName;
protected final ValueMapper mapper;
public AbstractConfig(String qualifiedName, ValueMapper mapper) {
this.qualifiedName = qualifiedName;
this.mapper = mapper;
}
@Override
public String getQualifiedName() {
return qualifiedName;
}
@Override
public Config get(String propertyName) {
var childValue = getRawChildValue(propertyName);
var childName = qualifiedName.isEmpty() ? propertyName : qualifiedName + '.' + propertyName;
if (childValue instanceof Composite) {
return new CompositeConfig(childName, mapper, (Composite) childValue);
}
if (childValue instanceof Map) {
return new MapConfig(childName, mapper, (Map<?, ?>) childValue);
}
return new LeafConfig(childName, mapper, childValue);
}
@Override
public <T> T as(Class<T> type) {
return as((Type) type);
}
@Override
public <T> T as(Type type) {
return mapper.map(getRawValue(), type);
}
@Override
public <T> T as(JavaType<T> javaType) {
return as(javaType.getType());
}
protected abstract Object getRawChildValue(String property);
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import org.pkl.config.java.mapper.ValueMapper;
import org.pkl.core.Composite;
class CompositeConfig extends AbstractConfig {
private final Composite composite;
CompositeConfig(String qualifiedName, ValueMapper mapper, Composite composite) {
super(qualifiedName, mapper);
this.composite = composite;
}
@Override
public Object getRawValue() {
return composite;
}
@Override
protected Object getRawChildValue(String propertyName) {
var result = composite.getPropertyOrNull(propertyName);
if (result != null) return result;
throw new NoSuchChildException(
String.format(
"Node `%s` of type `%s` does not have a property named `%s`. Available properties: %s",
getQualifiedName(),
composite.getClassInfo().getQualifiedName(),
propertyName,
composite.getProperties().keySet()),
propertyName);
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import java.lang.reflect.Type;
import org.pkl.config.java.mapper.ConversionException;
import org.pkl.core.Evaluator;
/**
* A root, intermediate, or leaf node in a configuration tree. Child nodes can be obtained by name
* using {@link #get(String)}. To consume the node's composite or scalar value, convert the value to
* the desired Java type, using one of the provided {@link #as} methods.
*/
public interface Config {
/**
* The dot-separated name of this node. For example, the node reached using {@code
* rootNode.get("foo").get("bar")} has qualified name {@code foo.bar}. Returns the empty String
* for the root node itself.
*/
String getQualifiedName();
/**
* The raw value of this node, as provided by {@link Evaluator}. Typically, a node's value is not
* consumed directly, but converted to the desired Java type using {@link #as}.
*/
Object getRawValue();
/**
* Returns the child node with the given unqualified name.
*
* @throws NoSuchChildException if a child with the given name does not exist
*/
Config get(String childName);
/**
* Converts this node's value to the given {@link Class}.
*
* @throws ConversionException if the value cannot be converted to the given type
*/
<T> T as(Class<T> type);
/**
* Converts this node's value to the given {@link Type}.
*
* <p>Note that usages of this methods are not type safe.
*
* @throws ConversionException if the value cannot be converted to the given type
*/
<T> T as(Type type);
/**
* Converts this node's value to the given {@link JavaType}.
*
* @throws ConversionException if the value cannot be converted to the given type
*/
<T> T as(JavaType<T> type);
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import org.pkl.config.java.mapper.ValueMapper;
import org.pkl.core.ModuleSource;
/**
* An evaluator that returns a {@link Config} tree.
*
* <p>Use {@link ConfigEvaluatorBuilder} to create instances of this type, configured according to
* your needs.
*/
public interface ConfigEvaluator extends AutoCloseable {
/** Shorthand for {@code ConfigEvaluatorBuilder.preconfigured().build()}. */
static ConfigEvaluator preconfigured() {
return ConfigEvaluatorBuilder.preconfigured().build();
}
/** Returns the underlying value mapper of this evaluator. */
ValueMapper getValueMapper();
/**
* Returns a new config evaluator with the same underlying evaluator and the given value mapper.
*/
ConfigEvaluator setValueMapper(ValueMapper mapper);
/** Evaluates the given module source into a {@link Config} tree. */
Config evaluate(ModuleSource moduleSource);
/**
* Releases all resources held by this evaluator. If an {@code evaluate} method is currently
* executing, this method blocks until cancellation of that execution has completed.
*
* <p>Once an evaluator has been closed, it can no longer be used, and calling {@code evaluate}
* methods will throw {@link IllegalStateException}. However, objects previously returned by
* {@code evaluate} methods remain valid.
*/
@Override
void close();
}

View File

@@ -0,0 +1,323 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.pkl.config.java.mapper.ValueMapperBuilder;
import org.pkl.core.EvaluatorBuilder;
import org.pkl.core.SecurityManager;
import org.pkl.core.StackFrameTransformer;
import org.pkl.core.project.DeclaredDependencies;
import org.pkl.core.project.Project;
import org.pkl.core.util.Nullable;
/** A builder for {@link ConfigEvaluator}s. */
@SuppressWarnings({"UnusedReturnValue", "unused"})
public final class ConfigEvaluatorBuilder {
private EvaluatorBuilder evaluatorBuilder;
private ValueMapperBuilder mapperBuilder;
private ConfigEvaluatorBuilder(
EvaluatorBuilder evaluatorBuilder, ValueMapperBuilder mapperBuilder) {
this.evaluatorBuilder = evaluatorBuilder;
this.mapperBuilder = mapperBuilder;
}
/** Creates a builder with preconfigured module evaluator and value mapper builders. */
public static ConfigEvaluatorBuilder preconfigured() {
return new ConfigEvaluatorBuilder(
EvaluatorBuilder.preconfigured(), ValueMapperBuilder.preconfigured());
}
/** Creates a builder with unconfigured module evaluator and value mapper builders. */
public static ConfigEvaluatorBuilder unconfigured() {
return new ConfigEvaluatorBuilder(
EvaluatorBuilder.unconfigured(), ValueMapperBuilder.unconfigured());
}
/**
* Sets the underlying module evaluator builder. When a config evaluator is built, the underlying
* module evaluator comes from this builder.
*/
public ConfigEvaluatorBuilder setEvaluatorBuilder(EvaluatorBuilder evaluatorBuilder) {
this.evaluatorBuilder = evaluatorBuilder;
return this;
}
/** Returns the currently set module evaluator builder. */
public EvaluatorBuilder getEvaluatorBuilder() {
return evaluatorBuilder;
}
/**
* Sets the underlying value mapper builder. When a config evaluator is built, the underlying
* value mapper comes from this builder.
*/
public ConfigEvaluatorBuilder setValueMapperBuilder(ValueMapperBuilder mapperBuilder) {
this.mapperBuilder = mapperBuilder;
return this;
}
/** Returns the currently set value mapper builder. */
public ValueMapperBuilder getValueMapperBuilder() {
return mapperBuilder;
}
/**
* Adds the given environment variable, overriding any environment variable previously added under
* the same name.
*
* <p>Modules can read environment variables with {@code read("env:<NAME>")}.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public ConfigEvaluatorBuilder addEnvironmentVariable(String name, String value) {
evaluatorBuilder.addEnvironmentVariable(name, value);
return this;
}
/**
* Adds the given environment variables, overriding any environment variables previously added
* under the same name.
*
* <p>Modules can read environment variables with {@code read("env:<NAME>")}.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public ConfigEvaluatorBuilder addEnvironmentVariables(Map<String, String> envVars) {
evaluatorBuilder.addEnvironmentVariables(envVars);
return this;
}
/**
* Removes any existing environment variables, then adds the given environment variables.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public ConfigEvaluatorBuilder setEnvironmentVariables(Map<String, String> envVars) {
evaluatorBuilder.setEnvironmentVariables(envVars);
return this;
}
/**
* Returns the currently set environment variables.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public Map<String, String> getEnvironmentVariables() {
return evaluatorBuilder.getEnvironmentVariables();
}
/**
* Adds the given external property, overriding any property previously set under the same name.
*
* <p>Modules can read external properties with {@code read("prop:<name>")}.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public ConfigEvaluatorBuilder addExternalProperty(String name, String value) {
evaluatorBuilder.addExternalProperty(name, value);
return this;
}
/**
* Adds the given external properties, overriding any properties previously set under the same
* name.
*
* <p>Modules can read external properties with {@code read("prop:<name>")}.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public ConfigEvaluatorBuilder addExternalProperties(Map<String, String> properties) {
evaluatorBuilder.addExternalProperties(properties);
return this;
}
/**
* Removes any existing external properties, then adds the given properties.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public ConfigEvaluatorBuilder setExternalProperties(Map<String, String> properties) {
evaluatorBuilder.setExternalProperties(properties);
return this;
}
/**
* Returns the currently set external properties.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public Map<String, String> getExternalProperties() {
return evaluatorBuilder.getExternalProperties();
}
/**
* Sets the given security manager, replacing any previously set security manager.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public ConfigEvaluatorBuilder setSecurityManager(SecurityManager manager) {
evaluatorBuilder.setSecurityManager(manager);
return this;
}
/**
* Returns the currently set security manager.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public @Nullable SecurityManager getSecurityManager() {
return evaluatorBuilder.getSecurityManager();
}
/**
* Sets the given stack frame transformer, replacing any previously set transformer.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public ConfigEvaluatorBuilder setStackFrameTransformer(
StackFrameTransformer stackFrameTransformer) {
evaluatorBuilder.setStackFrameTransformer(stackFrameTransformer);
return this;
}
/**
* Returns the currently set stack frame transformer.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public @Nullable StackFrameTransformer getStackFrameTransformer() {
return evaluatorBuilder.getStackFrameTransformer();
}
/**
* Sets the project for the evaluator, without applying evaluator settings in the project.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public ConfigEvaluatorBuilder setProjectDependencies(DeclaredDependencies dependencies) {
evaluatorBuilder.setProjectDependencies(dependencies);
return this;
}
/**
* Sets the project for the evaluator, and applies any settings if set.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*
* @throws IllegalStateException if {@link #setSecurityManager(SecurityManager)} was also called.
*/
public ConfigEvaluatorBuilder applyFromProject(Project project) {
evaluatorBuilder.applyFromProject(project);
return this;
}
/**
* Sets an evaluation timeout to be enforced by the {@link ConfigEvaluator}'s {@code evaluate}
* methods.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public ConfigEvaluatorBuilder setTimeout(Duration timeout) {
evaluatorBuilder.setTimeout(timeout);
return this;
}
/**
* Sets the set of URI patterns to be allowed when importing modules.
*
* <p>This is a convenieince method that delegates to the underlying evaluator builder.
*
* @throws IllegalStateException if {@link #setSecurityManager(SecurityManager)} was also called.
*/
public ConfigEvaluatorBuilder setAllowedModules(Collection<Pattern> patterns) {
evaluatorBuilder.setAllowedModules(patterns);
return this;
}
/**
* Returns the set of patterns to be allowed when importing modules.
*
* <p>This is a convenieince method that delegates to the underlying evaluator builder.
*/
public List<Pattern> getAllowedModules() {
return evaluatorBuilder.getAllowedModules();
}
/**
* Sets the set of URI patterns to be allowed when reading resources.
*
* <p>This is a convenieince method that delegates to the underlying evaluator builder.
*
* @throws IllegalStateException if {@link #setSecurityManager(SecurityManager)} was also called.
*/
public ConfigEvaluatorBuilder setAllowedResources(Collection<Pattern> patterns) {
evaluatorBuilder.setAllowedResources(patterns);
return this;
}
/**
* Returns the set of patterns to be allowed when reading resources.
*
* <p>This is a convenieince method that delegates to the underlying evaluator builder.
*/
public List<Pattern> getAllowedResources() {
return evaluatorBuilder.getAllowedResources();
}
/**
* Sets the root directory, which restricts access to file-based modules and resources located
* under this directory.
*
* <p>This is a convenieince method that delegates to the underlying evaluator builder.
*/
public ConfigEvaluatorBuilder setRootDir(@Nullable Path rootDir) {
evaluatorBuilder.setRootDir(rootDir);
return this;
}
/**
* Returns the currently set root directory, if set.
*
* <p>This is a convenieince method that delegates to the underlying evaluator builder.
*/
public @Nullable Path getRootDir() {
return evaluatorBuilder.getRootDir();
}
/**
* Returns the currently set evaluation timeout.
*
* <p>This is a convenience method that delegates to the underlying evaluator builder.
*/
public @Nullable Duration getTimeout() {
return evaluatorBuilder.getTimeout();
}
/**
* Builds a config evaluator whose underlying module evaluator and value mapper is built using the
* configured builders. The same builder can be used to build multiple config evaluators.
*/
public ConfigEvaluator build() {
return new ConfigEvaluatorImpl(evaluatorBuilder.build(), mapperBuilder.build());
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import org.pkl.config.java.mapper.ValueMapper;
import org.pkl.core.Evaluator;
import org.pkl.core.ModuleSource;
final class ConfigEvaluatorImpl implements ConfigEvaluator {
private final Evaluator evaluator;
private final ValueMapper mapper;
ConfigEvaluatorImpl(Evaluator evaluator, ValueMapper mapper) {
this.evaluator = evaluator;
this.mapper = mapper;
}
@Override
public Config evaluate(ModuleSource moduleSource) {
var module = evaluator.evaluate(moduleSource);
return new CompositeConfig("", mapper, module);
}
@Override
public ValueMapper getValueMapper() {
return mapper;
}
@Override
public ConfigEvaluator setValueMapper(ValueMapper mapper) {
return new ConfigEvaluatorImpl(evaluator, mapper);
}
@Override
public void close() {
evaluator.close();
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import java.lang.reflect.Type;
/**
* Thrown by {@link Config#as(Type)} when the determined Java class for a Pkl value cannot be found
* on the classpath.
*
* <p>When this happens, the most likely explanation is that the generated code is not up-to-date.
*/
public class InvalidMappingException extends RuntimeException {
String pklName;
String javaName;
public InvalidMappingException(String pklName, String javaName, Exception cause) {
super(cause);
this.pklName = pklName;
this.javaName = javaName;
}
@Override
public String getMessage() {
return "Did not find expected Java class `"
+ javaName
+ "` on the classpath for Pkl class `"
+ pklName
+ "`. Is your generated code up to date?";
}
public String getPklName() {
return pklName;
}
public String getJavaName() {
return javaName;
}
}

View File

@@ -0,0 +1,179 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import org.pkl.config.java.mapper.Types;
import org.pkl.core.Pair;
import org.pkl.core.util.Nullable;
/**
* Runtime representation of a possibly parameterized Java type. Factory methods are provided to
* ease construction of commonly used Java standard library types. For example, a {@code JavaType}
* for {@code List<String>} can be constructed using {@code JavaType.listOf(String.class)}.
*
* <p>Parameterizations of other types can be constructed using the <em>super type token</em> idiom:
*
* <p>
*
* <pre>{@code
* class Mapping<T> {} // some user-defined type
* Config config = ...
*
* Mapping<String> container = config.as(
* // construct super type token for Mapping<String>
* new JavaType<Mapping<String>>() {}
* );
* }</pre>
*
* @param <T> the type reified by this {@code JavaType} instance
*/
@SuppressWarnings("unused")
public class JavaType<T> {
private final Type type;
protected JavaType() {
var superclass = getClass().getGenericSuperclass();
if (superclass instanceof Class) {
throw new IllegalStateException("JavaType token must be parameterized.");
}
type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
private JavaType(Type type) {
this.type = type;
}
/** Creates a {@code JavaType} for the given {@code Class}. */
public static <T> JavaType<T> of(Class<T> type) {
return new JavaType<>(type);
}
/**
* Creates a {@code JavaType} for the given {@code Type}.
*
* <p>Note: This method is not type safe, and should be used with care.
*/
public static <T> JavaType<T> of(Type type) {
return new JavaType<>(type);
}
/** Creates an {@link Optional} type with the given element type. */
public static <E> JavaType<Optional<E>> optionalOf(Class<E> elementType) {
return JavaType.of(Types.optionalOf(elementType));
}
/** Creates an {@link Optional} type with the given element type. */
public static <E> JavaType<Optional<E>> optionalOf(JavaType<E> elementType) {
return JavaType.of(Types.optionalOf(elementType.type));
}
/** Creates a {@link Pair} type with the given first and second element types. */
public static <F, S> JavaType<Pair<F, S>> pairOf(Class<F> firstType, Class<S> secondType) {
return JavaType.of(Types.pairOf(firstType, secondType));
}
/** Creates a {@link Pair} type with the given first and second element types. */
public static <F, S> JavaType<Pair<F, S>> pairOf(JavaType<F> firstType, JavaType<S> secondType) {
return JavaType.of(Types.pairOf(firstType.type, secondType.type));
}
/** Creates an array type with the given element type. */
public static <E> JavaType<E[]> arrayOf(Class<E> elementType) {
return JavaType.of(Types.arrayOf(elementType));
}
/** Creates an array type with the given element type. */
public static <E> JavaType<E[]> arrayOf(JavaType<E> elementType) {
return JavaType.of(Types.arrayOf(elementType.type));
}
/** Creates an {@link Iterable} type with the given element type. */
public static <E> JavaType<Iterable<E>> iterableOf(Class<E> elementType) {
return JavaType.of(Types.iterableOf(elementType));
}
/** Creates an {@link Iterable} type with the given element type. */
public static <E> JavaType<Iterable<E>> iterableOf(JavaType<E> elementType) {
return JavaType.of(Types.iterableOf(elementType.type));
}
/** Creates a {@link Collection} type with the given element type. */
public static <E> JavaType<Collection<E>> collectionOf(Class<E> elementType) {
return JavaType.of(Types.collectionOf(elementType));
}
/** Creates a {@link Collection} type with the given element type. */
public static <E> JavaType<Collection<E>> collectionOf(JavaType<E> elementType) {
return JavaType.of(Types.collectionOf(elementType.type));
}
/** Creates a {@link List} type with the given element type. */
public static <E> JavaType<List<E>> listOf(Class<E> elementType) {
return JavaType.of(Types.listOf(elementType));
}
/** Creates a {@link List} type with the given element type. */
public static <E> JavaType<List<E>> listOf(JavaType<E> elementType) {
return JavaType.of(Types.listOf(elementType.type));
}
/** Creates a {@link Set} type with the given element type. */
public static <E> JavaType<Set<E>> setOf(Class<E> elementType) {
return JavaType.of(Types.setOf(elementType));
}
/** Creates a {@link Set} type with the given element type. */
public static <E> JavaType<Set<E>> setOf(JavaType<E> elementType) {
return JavaType.of(Types.setOf(elementType.type));
}
/** Creates a {@link Map} type with the given key and value types. */
public static <K, V> JavaType<Map<K, V>> mapOf(Class<K> keyType, Class<V> valueType) {
return JavaType.of(Types.mapOf(keyType, valueType));
}
/** Creates a {@link Map} type with the given key and value types. */
public static <K, V> JavaType<Map<K, V>> mapOf(JavaType<K> keyType, JavaType<V> valueType) {
return JavaType.of(Types.mapOf(keyType.type, valueType.type));
}
/** Returns the underlying {@link Type}. */
public Type getType() {
return type;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof JavaType)) return false;
var other = (JavaType<?>) obj;
return type.equals(other.type);
}
@Override
public int hashCode() {
return type.hashCode();
}
@Override
public String toString() {
return type.toString();
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import org.pkl.config.java.mapper.ValueMapper;
import org.pkl.core.PClassInfo;
class LeafConfig extends AbstractConfig {
private final Object value;
LeafConfig(String qualifiedName, ValueMapper mapper, Object value) {
super(qualifiedName, mapper);
this.value = value;
}
@Override
public Object getRawValue() {
return value;
}
@Override
protected Object getRawChildValue(String propertyName) {
throw new NoSuchChildException(
String.format(
"Leaf node `%s` of type `%s` does not have a child named `%s`.",
qualifiedName, PClassInfo.forValue(value).getQualifiedName(), propertyName),
propertyName);
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import java.util.Map;
import org.pkl.config.java.mapper.ValueMapper;
import org.pkl.core.PClassInfo;
class MapConfig extends AbstractConfig {
private final Map<?, ?> map;
MapConfig(String qualifiedName, ValueMapper mapper, Map<?, ?> map) {
super(qualifiedName, mapper);
this.map = map;
}
@Override
public Object getRawValue() {
return map;
}
@Override
protected Object getRawChildValue(String propertyName) {
var result = map.get(propertyName);
if (result != null) return result;
throw new NoSuchChildException(
String.format(
"Node `%s` of type `%s` does not have a key named `%s`. Available keys: %s",
getQualifiedName(), PClassInfo.Map.getQualifiedName(), propertyName, map.keySet()),
propertyName);
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
/**
* Thrown by {@link Config#get} when a child node with the given name does not exist, or when the
* current config node is a leaf node.
*/
public class NoSuchChildException extends RuntimeException {
private final String childName;
public NoSuchChildException(String message, String childName) {
super(message);
this.childName = childName;
}
public String getChildName() {
return childName;
}
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import org.pkl.config.java.InvalidMappingException;
import org.pkl.core.PClassInfo;
import org.pkl.core.util.Nullable;
/**
* Describes mappings of Pkl class names to their corresponding Java classes.
*
* <p>This is used by {@link ValueMapper} to pick the correct Java class when mapping Pkl into Java.
*
* <p>Mappings are determined by scanning the <code>
* /META-INF/org/pkl/config/java/mapper/classes</code> directory for properties files.
*
* <p>Property files should be in the form of <code>
* org.pkl.config.java.mapper.[PKL_CLASS_NAME]=[JAVA_REFLECTION_CLASS_NAME]</code>
*
* <p>Mappings are optional, and only required if Pkl types are polymorphic.
*
* <p>They are generated by the Java and Kotlin code generators, and can be handwritten if not using
* codegen.
*/
public class ClassRegistry {
private static final Properties classMappings = new Properties();
private static final Object lock = new Object();
private static final String CLASSES_DIRECTORY = "/META-INF/org/pkl/config/java/mapper/classes";
private static final String PREFIX = "org.pkl.config.java.mapper.";
private static final Set<String> loadedModules = new HashSet<>();
private ClassRegistry() {}
static @Nullable Class<?> get(PClassInfo<?> pklClassInfo) {
var pklModuleName = pklClassInfo.getModuleName();
var pklClassName = pklClassInfo.getQualifiedName();
initClassMappings(pklModuleName);
var javaName = classMappings.getProperty(PREFIX + pklClassInfo.getQualifiedName());
if (javaName == null) {
return null;
}
try {
return Class.forName(javaName);
} catch (ClassNotFoundException e) {
throw new InvalidMappingException(pklClassName, javaName, e);
}
}
private static void initClassMappings(String pklModuleName) {
synchronized (lock) {
if (loadedModules.contains(pklModuleName)) {
return;
}
loadedModules.add(pklModuleName);
var url =
ClassRegistry.class.getResourceAsStream(
CLASSES_DIRECTORY + "/" + pklModuleName + ".properties");
if (url == null) {
return;
}
try {
classMappings.load(url);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.reflect.Type;
import org.pkl.core.PClassInfo;
/**
* Describes a conversion from a Pkl source type to a (possibly parameterized) Java target type,
* performed by the given {@link Converter}.
*
* @param <S> Java type representing the Pkl source type
* @param <T> Java target type
*/
public final class Conversion<S, T> {
public final PClassInfo<S> sourceType;
public final Type targetType;
public final Converter<? super S, ? extends T> converter;
private Conversion(
PClassInfo<S> sourceType, Type targetType, Converter<? super S, ? extends T> converter) {
this.sourceType = sourceType;
this.targetType = targetType;
this.converter = converter;
}
/**
* Creates a conversion from the given Pkl source type to the given (possibly parameterized) Java
* type, using the given converter.
*/
public static <S, T> Conversion<S, T> of(
PClassInfo<S> sourceType, Type targetType, Converter<? super S, ? extends T> converter) {
return new Conversion<>(sourceType, targetType, converter);
}
/**
* Creates a conversion from the given Pkl source type to the given non-parameterized Java type,
* using the given converter. This overload is provided to allow for better type inference.
*/
public static <S, T> Conversion<S, T> of(
PClassInfo<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) {
return new Conversion<>(sourceType, targetType, converter);
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
/** Thrown when a {@link ValueMapper} conversion fails. */
public class ConversionException extends RuntimeException {
public ConversionException(String message) {
super(message);
}
public ConversionException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,337 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.*;
import java.util.regex.*;
import org.pkl.core.*;
/** Predefined conversions for scalar types. */
public final class Conversions {
private Conversions() {}
/**
* Conversion from {@code pkl.base#Int} to {@link Byte}. Throws {@link ConversionException} if the
* value is too large.
*/
public static final Conversion<Long, Byte> pIntToByte =
Conversion.of(
PClassInfo.Int,
byte.class,
(value, mapper) -> {
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
throw new ConversionException(
String.format(
"Cannot convert pkl.base#Int `%s` to java.lang.Byte because it is outside range `%s..%s`",
value, Byte.MIN_VALUE, Byte.MAX_VALUE));
}
return value.byteValue();
});
/**
* Conversion from {@code pkl.base#Int} to {@link Short}. Throws {@link ConversionException} if
* the value is too large.
*/
public static final Conversion<Long, Short> pIntToShort =
Conversion.of(
PClassInfo.Int,
short.class,
(value, mapper) -> {
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
throw new ConversionException(
String.format(
"Cannot convert pkl.base#Int `%s` to java.lang.Short because it is outside range `%s..%s`",
value, Short.MIN_VALUE, Short.MAX_VALUE));
}
return value.shortValue();
});
/**
* Conversion from {@code pkl.base#Int} to {@link Integer}. Throws {@link ConversionException} if
* the value is too large.
*/
public static final Conversion<Long, Integer> pIntToInteger =
Conversion.of(
PClassInfo.Int,
int.class,
(value, mapper) -> {
if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
throw new ConversionException(
String.format(
"Cannot convert pkl.base#Int `%s` to java.lang.Integer because it is outside range `%s..%s`",
value, Integer.MIN_VALUE, Integer.MAX_VALUE));
}
return value.intValue();
});
/** Conversion from {@code pkl.base#Int} to {@link Float}. May lose precision. */
public static final Conversion<Long, Float> pIntToFloat =
Conversion.of(PClassInfo.Int, float.class, (value, mapper) -> value.floatValue());
/** Conversion from {@code pkl.base#Int} to {@link Double}. May lose precision. */
public static final Conversion<Long, Double> pIntToDouble =
Conversion.of(PClassInfo.Int, double.class, (value, mapper) -> value.doubleValue());
/** Conversion from {@code pkl.base#Int} to {@link BigInteger}. */
public static final Conversion<Long, BigInteger> pIntToBigInteger =
Conversion.of(PClassInfo.Int, BigInteger.class, (value, mapper) -> BigInteger.valueOf(value));
/** Conversion from {@code pkl.base#Int} to {@link BigDecimal}. */
public static final Conversion<Long, BigDecimal> pIntToBigDecimal =
Conversion.of(PClassInfo.Int, BigDecimal.class, (value, mapper) -> BigDecimal.valueOf(value));
/** Conversion from {@code pkl.base#Float} to {@link Float}. May lose precision. */
public static final Conversion<Double, Float> pFloatToFloat =
Conversion.of(PClassInfo.Float, float.class, (value, mapper) -> value.floatValue());
/** Conversion from {@code pkl.base#Float} to {@link BigDecimal}. */
public static final Conversion<Double, BigDecimal> pFloatToBigDecimal =
Conversion.of(
PClassInfo.Float, BigDecimal.class, (value, mapper) -> BigDecimal.valueOf(value));
/**
* Conversion from {@code pkl.base#String} to {@link Character}. Throws {@link
* ConversionException} if the String value is not of length one.
*/
public static final Conversion<String, Character> pStringToCharacter =
Conversion.of(
PClassInfo.String,
Character.class,
(value, mapper) -> {
if (value.length() != 1) {
throw new ConversionException(
String.format(
"Cannot convert pkl.base#String `%s` to java.lang.Character because it is not of length 1.",
value));
}
return value.charAt(0);
});
/**
* Conversion from {@code pkl.base#String} to {@link URI}. Throws {@link ConversionException} if
* the String value is not a syntactically valid URI.
*/
public static final Conversion<String, URI> pStringToURI =
Conversion.of(
PClassInfo.String,
URI.class,
(value, mapper) -> {
try {
return new URI(value);
} catch (URISyntaxException e) {
throw new ConversionException(
"Failed to convert `pkl.base#String` to `java.net.URI`.", e);
}
});
/**
* Conversion from {@code pkl.base#String} to {@link URL}. Throws {@link ConversionException} if
* the String value is not a syntactically valid URL.
*/
public static final Conversion<String, URL> pStringToURL =
Conversion.of(
PClassInfo.String,
URL.class,
(value, mapper) -> {
try {
return new URL(value);
} catch (MalformedURLException e) {
throw new ConversionException(
"Failed to convert `pkl.base#String` to `java.net.URL`.", e);
}
});
/** Conversion from {@code pkl.base#String} to {@link File}. */
public static final Conversion<String, File> pStringToFile =
Conversion.of(PClassInfo.String, File.class, (value, mapper) -> new File(value));
/**
* Conversion from {@code pkl.base#String} to {@link Path}. Throws {@link ConversionException} if
* the String value is not a syntactically valid path.
*/
public static final Conversion<String, Path> pStringToPath =
Conversion.of(
PClassInfo.String,
Path.class,
(value, mapper) -> {
try {
return Path.of(value);
} catch (InvalidPathException e) {
throw new ConversionException(
"Failed to convert `pkl.base#String` to `java.nio.file.Path`.", e);
}
});
/** Conversion from {@code pkl.base#String} to {@link Pattern}. */
public static final Conversion<String, Pattern> pStringToPattern =
Conversion.of(
PClassInfo.String,
Pattern.class,
(value, mapper) -> {
try {
return Pattern.compile(value);
} catch (PatternSyntaxException e) {
throw new ConversionException(
"Failed to convert `pkl.base#String` to `java.util.regex.Pattern`.", e);
}
});
/** Conversion from {@code pkl.base#Regex} to {@link String}. */
public static final Conversion<Pattern, String> pRegexToString =
Conversion.of(PClassInfo.Regex, String.class, (value, mapper) -> value.pattern());
/** Conversion from {@code pkl.base#Duration} to {@link java.time.Duration}. */
public static final Conversion<Duration, java.time.Duration> pDurationToDuration =
Conversion.of(
PClassInfo.Duration, java.time.Duration.class, (value, mapper) -> value.toJavaDuration());
/** Conversion from {@code pkl.semver#Version} to {@link Version}. */
// Cannot leave this to `ConverterFactories.pObjectToDataObject`
// because `Version` is part of pkl-core and thus cannot be annotated with `@Named`.
public static final Conversion<PObject, Version> pVersionToVersion =
Conversion.of(
PClassInfo.Version,
Version.class,
(value, mapper) -> {
try {
return new Version(
Math.toIntExact((Long) value.getProperty("major")),
Math.toIntExact((Long) value.getProperty("minor")),
Math.toIntExact((Long) value.getProperty("patch")),
(String) value.get("preRelease"),
(String) value.get("build"));
} catch (ArithmeticException e) {
throw new ConversionException(
"Failed to convert `pkl.semver#Version` to `org.pkl.core.Version`.", e);
}
});
public static final Conversion<PObject, String> pVersionToString =
Conversion.of(
PClassInfo.Version,
String.class,
(value, mapper) -> {
var builder = new StringBuilder();
builder.append(value.get("major"));
builder.append('.');
builder.append(value.get("minor"));
builder.append('.');
builder.append(value.get("patch"));
var preRelease = value.get("preRelease");
if (preRelease != null) {
builder.append('-');
builder.append(preRelease);
}
var build = value.get("build");
if (build != null) {
builder.append('+');
builder.append(build);
}
return builder.toString();
});
public static final Conversion<String, Version> pStringToVersion =
Conversion.of(
PClassInfo.String,
Version.class,
(value, mapper) -> {
try {
return Version.parse(value);
} catch (IllegalArgumentException e) {
throw new ConversionException(
"Failed to convert `pkl.base#String` to `org.pkl.core.Version`.", e);
}
});
/**
* Identity conversions used when the Java representation of the Pkl type matches the target type
* or when the target type is {@link Object}.
*/
public static final Collection<Conversion<?, ?>> identities =
List.of(
Conversion.of(PClassInfo.Boolean, boolean.class, Converter.identity()),
Conversion.of(PClassInfo.Boolean, Object.class, Converter.identity()),
Conversion.of(PClassInfo.String, String.class, Converter.identity()),
Conversion.of(PClassInfo.String, Object.class, Converter.identity()),
Conversion.of(PClassInfo.Int, long.class, Converter.identity()),
Conversion.of(PClassInfo.Int, Number.class, Converter.identity()),
Conversion.of(PClassInfo.Int, Object.class, Converter.identity()),
Conversion.of(PClassInfo.Float, double.class, Converter.identity()),
Conversion.of(PClassInfo.Float, Number.class, Converter.identity()),
Conversion.of(PClassInfo.Float, Object.class, Converter.identity()),
Conversion.of(PClassInfo.Duration, Duration.class, Converter.identity()),
Conversion.of(PClassInfo.Duration, Object.class, Converter.identity()),
Conversion.of(PClassInfo.DataSize, DataSize.class, Converter.identity()),
Conversion.of(PClassInfo.DataSize, Object.class, Converter.identity()),
Conversion.of(PClassInfo.Module, PModule.class, Converter.identity()),
Conversion.of(PClassInfo.Module, Object.class, Converter.identity()),
Conversion.of(PClassInfo.Class, PClass.class, Converter.identity()),
Conversion.of(PClassInfo.Class, Object.class, Converter.identity()),
Conversion.of(PClassInfo.Regex, Pattern.class, Converter.identity()),
Conversion.of(PClassInfo.Regex, Object.class, Converter.identity()),
Conversion.of(PClassInfo.Null, PNull.class, Converter.identity())
// PClassInfo.Null -> Object.class is covered by PNullToAny (returns null rather than
// PNull.getInstance())
);
/** Numeric conversions. Does not include identity conversions. */
public static final Collection<Conversion<?, ?>> numeric =
List.of(
pIntToByte,
pIntToShort,
pIntToInteger,
pIntToFloat,
pIntToDouble,
pIntToBigInteger,
pIntToBigDecimal,
pFloatToFloat,
pFloatToBigDecimal);
/** Conversions that don't fit any other category. */
public static final Collection<Conversion<?, ?>> misc =
List.of(
pStringToCharacter,
pStringToURI,
pStringToURL,
pStringToFile,
pStringToPath,
pStringToPattern,
pRegexToString,
pDurationToDuration,
pVersionToVersion,
pVersionToString,
pStringToVersion);
/** All conversions defined in this class. */
public static final Collection<Conversion<?, ?>> all = collectAll();
private static Collection<Conversion<?, ?>> collectAll() {
var result = new ArrayList<Conversion<?, ?>>(identities.size() + numeric.size() + misc.size());
result.addAll(identities);
result.addAll(numeric);
result.addAll(misc);
return Collections.unmodifiableList(result);
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
/**
* Converter for a particular source and target type.
*
* @param <S> the converter's source type
* @param <T> the converter's target type
*/
@FunctionalInterface
public interface Converter<S, T> {
/**
* Converts the given value. The given {@link ValueMapper} can be used to convert nested values of
* composite values (objects, collections, etc.).
*/
T convert(S value, ValueMapper valueMapper);
/** Returns an identity converter for the requested type. */
static <S> Converter<S, S> identity() {
return (value, valueMapper) -> value;
}
}

View File

@@ -0,0 +1,103 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.util.*;
import org.pkl.core.PObject;
import org.pkl.core.Pair;
/** Predefined conversions for composite types (objects, collections, etc.). */
public final class ConverterFactories {
private ConverterFactories() {}
/**
* Conversion from {@code pkl.base#Null} to any non-primitive type. The conversion result is
* always {@code null}.
*/
public static final ConverterFactory pNullToAny = new PNullToAny();
/** Identity conversion for {@link PObject}. */
public static final ConverterFactory pObjectToPObject = new PObjectToPObject();
/**
* Conversion from {@code pkl.base#String} to Java Enum type. If there is no exact match between
* string and enum value, some variations are tried. For example, both {@code "house-of-cards"}
* and {@code "house of cards"} will be successfully matched to enum value {@code HOUSE_OF_CARDS}.
*/
public static final ConverterFactory pStringToEnum = new PStringToEnum();
/**
* Conversion from any Pkl value to {@link java.util.Optional}. Returns an empty optional for
* {@code pkl.base#Null} and a present optional otherwise.
*/
public static final ConverterFactory pAnyToOptional = new PAnyToOptional();
/** Conversion from {@code pkl.base#Collection} to Java primitive or object array. */
public static final ConverterFactory pCollectionToArray = new PCollectionToArray();
/**
* Conversion from {@code pkl.base#Collection} to {@link Collection}. The concrete implementation
* type is determined using {@link TypeMapping}s.
*/
public static final ConverterFactory pCollectionToCollection = new PCollectionToCollection();
/**
* Conversion from {@code pkl.base#Map} to {@link Map}. The concrete implementation type is
* determined using {@link TypeMapping}s.
*/
public static final ConverterFactory pMapToMap = new PMapToMap();
/**
* Conversion from Pkl module or object to Java data object. The conversion is performed as
* follows:
*
* <p>
*
* <ol>
* <li>Find the Java class constructor with the highest number of parameters.
* <li>Correlate constructor parameters with Pkl object properties by name.
* <li>Convert each Pkl property value to the corresponding constructor parameter's type.
* <li>Invoke the constructor.
* </ol>
*
* <p>Dynamic and class based Pkl objects are equally supported. The Pkl object must contain all
* properties defined by the Java class constructor. Any additional Pkl object properties are
* ignored.
*
* <p>Unless the Java 8+ compiler option {@code -parameters} is set, constructor parameters must
* be annotated with {@link Named} or {@code javax.inject.Named}.
*/
public static final ConverterFactory pObjectToDataObject = new PObjectToDataObject();
public static final ConverterFactory pObjectToMap = new PObjectToMap();
/** Conversion from {@code pkl.base#Pair} to {@link Pair}. */
public static final ConverterFactory pPairToPair = new PPairToPair();
/** All conversions defined in this class. */
public static final Collection<ConverterFactory> all =
List.of(
pAnyToOptional,
pNullToAny,
pObjectToPObject,
pStringToEnum,
pCollectionToArray,
pCollectionToCollection,
pMapToMap,
pObjectToDataObject,
pObjectToMap,
pPairToPair);
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.reflect.Type;
import java.util.Optional;
import java.util.function.Predicate;
import org.pkl.core.PClassInfo;
/**
* A factory for {@link Converter}s. Used to implement conversions to generic Java classes. In such
* a case a single {@link Converter} does not suffice. Instead the factory creates a new converter
* for every parameterization of the target type. Once created, the converter is cached for later
* use, and the factory is never again invoked for the same parameterized target type.
*
* <p>For best performace, all introspection of target types (for example using {@link Reflection})
* should happen in the factory rather then the returned converters.
*/
@FunctionalInterface
public interface ConverterFactory {
/**
* Returns a converter for the given source and target types, or {@code Optional.empty()} if the
* factory cannot handle the requested types.
*/
// idea: return Success/Failure providing an explanation of why this factory wasn't applicable
Optional<Converter<?, ?>> create(PClassInfo<?> sourceType, Type targetType);
/**
* Returns a new factory that restricts use of this factory to target types for which the given
* predicate holds.
*/
default ConverterFactory when(Predicate<Type> predicate) {
return (sourceType, targetType) -> {
if (!predicate.test(targetType)) return Optional.empty();
return create(sourceType, targetType);
};
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Names a constructor parameter for Pkl-to-Java object mapping. Alternatively, the {@code
* javax.inject.Named} annotation can be used, or parameter names can be retained by setting the
* Java 8+ compiler option {@code -parameters}.
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Named {
String value();
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Indicates that a type does not accept {@code null} as a value. */
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.CLASS)
@Documented
public @interface NonNull {}

View File

@@ -0,0 +1,52 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Optional;
import org.pkl.core.PClassInfo;
import org.pkl.core.PNull;
final class PAnyToOptional implements ConverterFactory {
@Override
public Optional<Converter<?, ?>> create(PClassInfo<?> sourceType, Type targetType) {
if (!(Reflection.toRawType(targetType) == Optional.class)) {
return Optional.empty();
}
// may seem redundant but is used to handle case where targetType is erased
var optionalType = (ParameterizedType) Reflection.getExactSupertype(targetType, Optional.class);
var elementType = optionalType.getActualTypeArguments()[0];
return Optional.of(new ConverterImpl(elementType));
}
private static class ConverterImpl implements Converter<Object, Optional<?>> {
private final Type elementType;
public ConverterImpl(Type elementType) {
this.elementType = elementType;
}
@Override
public Optional<?> convert(Object value, ValueMapper valueMapper) {
return value instanceof PNull
? Optional.empty()
: Optional.of(valueMapper.map(value, elementType));
}
}
}

View File

@@ -0,0 +1,266 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Optional;
import org.pkl.core.PClassInfo;
import org.pkl.core.util.Nullable;
final class PCollectionToArray implements ConverterFactory {
@Override
public Optional<Converter<?, ?>> create(PClassInfo<?> sourceType, Type targetType) {
var targetClass = Reflection.toRawType(targetType);
if (!(sourceType.isConcreteCollectionClass() && targetClass.isArray())) {
return Optional.empty();
}
if (targetClass.getComponentType().isPrimitive()) {
if (targetClass == boolean[].class) {
return Optional.of(new BooleanArrayConverterImpl());
}
if (targetClass == char[].class) {
return Optional.of(new CharArrayConverterImpl());
}
if (targetClass == long[].class) {
return Optional.of(new LongArrayConverterImpl());
}
if (targetClass == int[].class) {
return Optional.of(new IntArrayConverterImpl());
}
if (targetClass == short[].class) {
return Optional.of(new ShortArrayConverterImpl());
}
if (targetClass == byte[].class) {
return Optional.of(new ByteArrayConverterImpl());
}
if (targetClass == double[].class) {
return Optional.of(new DoubleArrayConverterImpl());
}
if (targetClass == float[].class) {
return Optional.of(new FloatArrayConverterImpl());
}
throw new AssertionError("unreachable code");
}
var elementType = Reflection.getArrayElementType(targetType);
return Optional.of(new ObjectArrayConverterImpl<>(elementType));
}
// having a separate converter for each primitive array type
// saves some reflection at the expense of some code duplication
private static final class BooleanArrayConverterImpl
implements Converter<Collection<Object>, boolean[]> {
private PClassInfo<Object> cachedElementType = PClassInfo.Unavailable;
private @Nullable Converter<Object, Boolean> cachedConverter;
@Override
public boolean[] convert(Collection<Object> value, ValueMapper valueMapper) {
var result = new boolean[value.size()];
var i = 0;
for (Object elem : value) {
if (!cachedElementType.isExactClassOf(elem)) {
cachedElementType = PClassInfo.forValue(elem);
cachedConverter = valueMapper.getConverter(cachedElementType, boolean.class);
}
assert cachedConverter != null;
result[i++] = cachedConverter.convert(elem, valueMapper);
}
return result;
}
}
private static final class CharArrayConverterImpl
implements Converter<Collection<Object>, char[]> {
private PClassInfo<Object> cachedElementType = PClassInfo.Unavailable;
private @Nullable Converter<Object, Character> cachedConverter;
@Override
public char[] convert(Collection<Object> value, ValueMapper valueMapper) {
var result = new char[value.size()];
var i = 0;
for (var elem : value) {
if (!cachedElementType.isExactClassOf(elem)) {
cachedElementType = PClassInfo.forValue(elem);
cachedConverter = valueMapper.getConverter(cachedElementType, char.class);
}
assert cachedConverter != null;
result[i++] = cachedConverter.convert(elem, valueMapper);
}
return result;
}
}
private static final class ByteArrayConverterImpl
implements Converter<Collection<Object>, byte[]> {
private PClassInfo<Object> cachedElementType = PClassInfo.Unavailable;
private @Nullable Converter<Object, Byte> cachedConverter;
@Override
public byte[] convert(Collection<Object> value, ValueMapper valueMapper) {
var result = new byte[value.size()];
var i = 0;
for (Object elem : value) {
if (!cachedElementType.isExactClassOf(elem)) {
cachedElementType = PClassInfo.forValue(elem);
cachedConverter = valueMapper.getConverter(cachedElementType, byte.class);
}
assert cachedConverter != null;
result[i++] = cachedConverter.convert(elem, valueMapper);
}
return result;
}
}
private static final class ShortArrayConverterImpl
implements Converter<Collection<Object>, short[]> {
private PClassInfo<Object> cachedElementType = PClassInfo.Unavailable;
private @Nullable Converter<Object, Short> cachedConverter;
@Override
public short[] convert(Collection<Object> value, ValueMapper valueMapper) {
var result = new short[value.size()];
var i = 0;
for (Object elem : value) {
if (!cachedElementType.isExactClassOf(elem)) {
cachedElementType = PClassInfo.forValue(elem);
cachedConverter = valueMapper.getConverter(cachedElementType, short.class);
}
assert cachedConverter != null;
result[i++] = cachedConverter.convert(elem, valueMapper);
}
return result;
}
}
private static final class IntArrayConverterImpl implements Converter<Collection<Object>, int[]> {
private PClassInfo<Object> cachedElementType = PClassInfo.Unavailable;
private @Nullable Converter<Object, Integer> cachedConverter;
@Override
public int[] convert(Collection<Object> value, ValueMapper valueMapper) {
var result = new int[value.size()];
var i = 0;
for (Object elem : value) {
if (!cachedElementType.isExactClassOf(elem)) {
cachedElementType = PClassInfo.forValue(elem);
cachedConverter = valueMapper.getConverter(cachedElementType, int.class);
}
assert cachedConverter != null;
result[i++] = cachedConverter.convert(elem, valueMapper);
}
return result;
}
}
private static final class LongArrayConverterImpl
implements Converter<Collection<Object>, long[]> {
private PClassInfo<Object> cachedElementType = PClassInfo.Unavailable;
private @Nullable Converter<Object, Long> cachedConverter;
@Override
public long[] convert(Collection<Object> value, ValueMapper valueMapper) {
var result = new long[value.size()];
var i = 0;
for (Object elem : value) {
if (!cachedElementType.isExactClassOf(elem)) {
cachedElementType = PClassInfo.forValue(elem);
cachedConverter = valueMapper.getConverter(cachedElementType, long.class);
}
assert cachedConverter != null;
result[i++] = cachedConverter.convert(elem, valueMapper);
}
return result;
}
}
private static final class FloatArrayConverterImpl
implements Converter<Collection<Object>, float[]> {
private PClassInfo<Object> cachedElementType = PClassInfo.Unavailable;
private @Nullable Converter<Object, Float> cachedConverter;
@Override
public float[] convert(Collection<Object> value, ValueMapper valueMapper) {
var result = new float[value.size()];
var i = 0;
for (Object elem : value) {
if (!cachedElementType.isExactClassOf(elem)) {
cachedElementType = PClassInfo.forValue(elem);
cachedConverter = valueMapper.getConverter(cachedElementType, float.class);
}
assert cachedConverter != null;
result[i++] = cachedConverter.convert(elem, valueMapper);
}
return result;
}
}
private static final class DoubleArrayConverterImpl
implements Converter<Collection<Object>, double[]> {
private PClassInfo<Object> cachedElementType = PClassInfo.Unavailable;
private @Nullable Converter<Object, Double> cachedConverter;
@Override
public double[] convert(Collection<Object> value, ValueMapper valueMapper) {
var result = new double[value.size()];
var i = 0;
for (Object elem : value) {
if (!cachedElementType.isExactClassOf(elem)) {
cachedElementType = PClassInfo.forValue(elem);
cachedConverter = valueMapper.getConverter(cachedElementType, double.class);
}
assert cachedConverter != null;
result[i++] = cachedConverter.convert(elem, valueMapper);
}
return result;
}
}
private static final class ObjectArrayConverterImpl<T>
implements Converter<Collection<Object>, T[]> {
private final Type componentType;
private final Class<T> rawComponentType;
private PClassInfo<Object> cachedElementType = PClassInfo.Unavailable;
private @Nullable Converter<Object, T> cachedConverter;
private ObjectArrayConverterImpl(Type componentType) {
this.componentType = componentType;
@SuppressWarnings("unchecked")
var rawComponentType = (Class<T>) Reflection.toRawType(componentType);
this.rawComponentType = rawComponentType;
}
@Override
public T[] convert(Collection<Object> value, ValueMapper valueMapper) {
@SuppressWarnings("unchecked")
var result = (T[]) Array.newInstance(rawComponentType, value.size());
var i = 0;
for (Object elem : value) {
if (!cachedElementType.isExactClassOf(elem)) {
cachedElementType = PClassInfo.forValue(elem);
cachedConverter = valueMapper.getConverter(cachedElementType, componentType);
}
assert cachedConverter != null;
result[i++] = cachedConverter.convert(elem, valueMapper);
}
return result;
}
}
}

View File

@@ -0,0 +1,133 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Optional;
import java.util.function.Function;
import org.pkl.core.PClassInfo;
import org.pkl.core.util.Nullable;
class PCollectionToCollection implements ConverterFactory {
private static final Lookup lookup = MethodHandles.lookup();
@Override
public Optional<Converter<?, ?>> create(PClassInfo<?> sourceType, Type targetType) {
var targetClass = Reflection.toRawType(targetType);
if (!(sourceType.isConcreteCollectionClass()
&& Collection.class.isAssignableFrom(targetClass))) {
return Optional.empty();
}
var iterableType =
(ParameterizedType) Reflection.getExactSupertype(targetType, Collection.class);
var elementType = Reflection.normalize(iterableType.getActualTypeArguments()[0]);
return createInstantiator(targetClass)
.map(instantiator -> new ConverterImpl<>(instantiator, elementType));
}
private <T> Optional<Function<Integer, Collection<T>>> createInstantiator(Class<T> clazz) {
try {
try {
// constructor with capacity and load factor parameters, e.g. HashSet
var ctor2 =
lookup.findConstructor(
clazz, MethodType.methodType(void.class, int.class, float.class));
return Optional.of(
length -> {
try {
//noinspection unchecked
return (Collection<T>) ctor2.invoke((int) (length / .75f) + 1, .75f);
} catch (Throwable t) {
throw new ConversionException(
String.format("Error invoking constructor of class `%s`.", clazz), t);
}
});
} catch (NoSuchMethodException e2) {
try {
// constructor with size parameter, e.g. ArrayList
var ctor1 = lookup.findConstructor(clazz, MethodType.methodType(void.class, int.class));
return Optional.of(
length -> {
try {
//noinspection unchecked
return (Collection<T>) ctor1.invoke(length);
} catch (Throwable t) {
throw new ConversionException(
String.format("Error invoking constructor of class `%s`.", clazz), t);
}
});
} catch (NoSuchMethodException e1) {
try {
// default constructor
var ctor0 = lookup.findConstructor(clazz, MethodType.methodType(void.class));
return Optional.of(
length -> {
try {
//noinspection unchecked
return (Collection<T>) ctor0.invoke();
} catch (Throwable t) {
throw new ConversionException(
String.format("Error invoking constructor of class `%s`.", clazz), t);
}
});
} catch (NoSuchMethodException e0) {
return Optional.empty();
}
}
}
} catch (IllegalAccessException e) {
throw new ConversionException(
String.format("Error accessing constructor of class `%s`.", clazz), e);
}
}
private static class ConverterImpl<T> implements Converter<Collection<Object>, Collection<T>> {
private final Function<Integer, Collection<T>> targetInstantiator;
private final Type targetElementType;
private PClassInfo<Object> cachedElementType = PClassInfo.Unavailable;
private @Nullable Converter<Object, T> cachedConverter;
private ConverterImpl(
Function<Integer, Collection<T>> targetInstantiator, Type targetElementType) {
this.targetInstantiator = targetInstantiator;
this.targetElementType = targetElementType;
}
@Override
public Collection<T> convert(Collection<Object> value, ValueMapper valueMapper) {
var result = targetInstantiator.apply(value.size());
for (Object elem : value) {
if (!cachedElementType.isExactClassOf(elem)) {
cachedElementType = PClassInfo.forValue(elem);
cachedConverter = valueMapper.getConverter(cachedElementType, targetElementType);
}
assert cachedConverter != null;
result.add(cachedConverter.convert(elem, valueMapper));
}
return result;
}
}
}

View File

@@ -0,0 +1,140 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.function.Function;
import org.pkl.core.PClassInfo;
import org.pkl.core.util.Nullable;
class PMapToMap implements ConverterFactory {
private static final Lookup lookup = MethodHandles.lookup();
@Override
public Optional<Converter<?, ?>> create(PClassInfo<?> sourceType, Type targetType) {
var targetClass = Reflection.toRawType(targetType);
if (!(sourceType == PClassInfo.Map && Map.class.isAssignableFrom(targetClass))) {
return Optional.empty();
}
ParameterizedType mapType;
if (Properties.class.isAssignableFrom(targetClass)) {
// Properties is-a Map<Object,Object> but is supposed to only contain String keys/values
mapType = Types.mapOf(String.class, String.class);
} else {
mapType = (ParameterizedType) Reflection.getExactSupertype(targetType, Map.class);
}
var typeArguments = mapType.getActualTypeArguments();
var keyType = Reflection.normalize(typeArguments[0]);
var valueType = Reflection.normalize(typeArguments[1]);
return createInstantiator(targetClass)
.map(instantiator -> new ConverterImpl<>(instantiator, keyType, valueType));
}
private <K, V> Optional<Function<Integer, Map<K, V>>> createInstantiator(Class<?> clazz) {
try {
// constructor with capacity and load factor arguments
var ctor2 =
lookup.findConstructor(clazz, MethodType.methodType(void.class, int.class, float.class));
return Optional.of(
length -> {
try {
//noinspection unchecked
return (Map<K, V>) ctor2.invoke((int) (length / .75f) + 1, .75f);
} catch (Throwable t) {
throw new ConversionException(
String.format("Error invoking constructor of class `%s`.", clazz), t);
}
});
} catch (NoSuchMethodException e2) {
try {
// default constructor
var ctor0 = lookup.findConstructor(clazz, MethodType.methodType(void.class));
return Optional.of(
length -> {
try {
//noinspection unchecked
return (Map<K, V>) ctor0.invoke();
} catch (Throwable t) {
throw new ConversionException(
String.format("Error invoking constructor of class `%s`.", clazz), t);
}
});
} catch (NoSuchMethodException e0) {
return Optional.empty();
} catch (IllegalAccessException e) {
throw new ConversionException(
String.format("Error accessing constructor of class `%s`.", clazz), e);
}
} catch (IllegalAccessException e) {
throw new ConversionException(
String.format("Error accessing constructor of class `%s`.", clazz), e);
}
}
private static class ConverterImpl<K, V> implements Converter<Map<Object, Object>, Map<K, V>> {
private final Function<Integer, Map<K, V>> targetInstantiator;
private final Type targetKeyType;
private final Type targetValueType;
private PClassInfo<Object> cachedKeyType = PClassInfo.Unavailable;
private @Nullable Converter<Object, K> cachedKeyConverter;
private PClassInfo<Object> cachedValueType = PClassInfo.Unavailable;
private @Nullable Converter<Object, V> cachedValueConverter;
private ConverterImpl(
Function<Integer, Map<K, V>> targetInstantiator, Type targetKeyType, Type targetValueType) {
this.targetInstantiator = targetInstantiator;
this.targetKeyType = targetKeyType;
this.targetValueType = targetValueType;
}
@Override
public Map<K, V> convert(Map<Object, Object> map, ValueMapper valueMapper) {
var result = targetInstantiator.apply(map.size());
for (Map.Entry<Object, Object> entry : map.entrySet()) {
var key = entry.getKey();
if (!cachedKeyType.isExactClassOf(key)) {
cachedKeyType = PClassInfo.forValue(key);
cachedKeyConverter = valueMapper.getConverter(cachedKeyType, targetKeyType);
}
assert cachedKeyConverter != null;
var value = entry.getValue();
if (!cachedValueType.isExactClassOf(value)) {
cachedValueType = PClassInfo.forValue(value);
cachedValueConverter = valueMapper.getConverter(cachedValueType, targetValueType);
}
assert cachedValueConverter != null;
result.put(
cachedKeyConverter.convert(key, valueMapper),
cachedValueConverter.convert(value, valueMapper));
}
return result;
}
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.reflect.Type;
import java.util.Optional;
import org.pkl.core.PClassInfo;
final class PNullToAny implements ConverterFactory {
@Override
public Optional<Converter<?, ?>> create(PClassInfo<?> sourceType, Type targetType) {
if (sourceType != PClassInfo.Null
|| (targetType instanceof Class && ((Class<?>) targetType).isPrimitive())) {
return Optional.empty();
}
//noinspection ConstantConditions
return Optional.of((value, valueMapper) -> null);
}
}

View File

@@ -0,0 +1,232 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.*;
import java.util.*;
import org.pkl.core.Composite;
import org.pkl.core.PClassInfo;
import org.pkl.core.PObject;
import org.pkl.core.util.Nullable;
public class PObjectToDataObject implements ConverterFactory {
private static final Lookup lookup = MethodHandles.lookup();
@SuppressWarnings("unchecked")
private static final @Nullable Class<? extends Annotation> javaxInjectNamedClass =
(Class<? extends Annotation>) Reflection.tryLoadClass("javax.inject.Named");
private static final @Nullable Method javaxInjectNamedValueMethod;
static {
try {
javaxInjectNamedValueMethod =
javaxInjectNamedClass == null ? null : javaxInjectNamedClass.getMethod("value");
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
protected PObjectToDataObject() {}
@Override
public final Optional<Converter<?, ?>> create(PClassInfo<?> sourceType, Type targetType) {
if (!(sourceType == PClassInfo.Module || sourceType.getJavaClass() == PObject.class)) {
return Optional.empty();
}
return selectConstructor(Reflection.toRawType(targetType))
.flatMap(
constructor ->
getParameters(constructor, targetType)
.map(
parameters -> {
try {
return new ConverterImpl<>(
targetType, lookup.unreflectConstructor(constructor), parameters);
} catch (IllegalAccessException e) {
throw new ConversionException(
String.format("Error accessing constructor `%s`.", constructor), e);
}
}));
}
protected Optional<Constructor<?>> selectConstructor(Class<?> clazz) {
return Arrays.stream(clazz.getDeclaredConstructors())
.max(Comparator.comparingInt(Constructor::getParameterCount));
}
protected Optional<List<String>> getParameterNames(Constructor<?> constructor) {
var paramNames = new ArrayList<String>(constructor.getParameterCount());
var properties = getAnnotation(constructor, ConstructorProperties.class);
if (properties != null) {
return Optional.of(Arrays.asList(properties.value()));
}
for (Parameter parameter : constructor.getParameters()) {
var name = getParameterName(parameter);
if (name == null) return Optional.empty();
paramNames.add(name);
}
return Optional.of(paramNames);
}
private Optional<List<Tuple2<String, Type>>> getParameters(
Constructor<?> constructor, Type targetType) {
return getParameterNames(constructor)
.map(
paramNames -> {
var paramTypes = Reflection.getExactParameterTypes(constructor, targetType);
var parameters = new ArrayList<Tuple2<String, Type>>(paramNames.size());
for (int i = 0; i < paramNames.size(); i++) {
var name = paramNames.get(i);
parameters.add(Tuple2.of(name, paramTypes[i]));
}
return parameters;
});
}
private static @Nullable String getParameterName(Parameter parameter) {
if (parameter.isNamePresent()) {
return parameter.getName();
}
Named named = getAnnotation(parameter, Named.class);
if (named != null) {
return named.value();
}
if (javaxInjectNamedClass != null) {
assert javaxInjectNamedValueMethod != null;
var ann = getAnnotation(parameter, javaxInjectNamedClass);
if (ann != null) {
try {
return (String) javaxInjectNamedValueMethod.invoke(ann);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new ConversionException("Failed to invoke `javax.inject.Named.value()`.", e);
}
}
}
return null;
}
private static @Nullable <T extends Annotation> T getAnnotation(
Constructor<?> constructor, Class<T> annotationClass) {
try {
return constructor.getAnnotation(annotationClass);
} catch (IndexOutOfBoundsException e) {
// workaround for https://bugs.openjdk.java.net/browse/JDK-8025806
return null;
}
}
private static @Nullable <T extends Annotation> T getAnnotation(
Parameter parameter, Class<T> annotationClass) {
try {
return parameter.getAnnotation(annotationClass);
} catch (
IndexOutOfBoundsException
e) { // workaround for https://bugs.openjdk.java.net/browse/JDK-8025806
return null;
}
}
private static class ConverterImpl<T> implements Converter<Composite, T> {
private final Type targetType;
private final MethodHandle constructorHandle;
private final Collection<Tuple2<String, Type>> parameters;
private final PClassInfo<Object>[] cachedPropertyTypes;
private final Converter<Object, T>[] cachedConverters;
ConverterImpl(
Type targetType,
MethodHandle constructorHandle,
Collection<Tuple2<String, Type>> parameters) {
this.targetType = targetType;
this.constructorHandle = constructorHandle;
this.parameters = parameters;
@SuppressWarnings("unchecked")
PClassInfo<Object>[] cachedPropertyTypes = new PClassInfo[parameters.size()];
this.cachedPropertyTypes = cachedPropertyTypes;
Arrays.fill(cachedPropertyTypes, PClassInfo.Unavailable);
@SuppressWarnings("unchecked")
Converter<Object, T>[] cachedConverters = new Converter[parameters.size()];
this.cachedConverters = cachedConverters;
}
@Override
public T convert(Composite value, ValueMapper valueMapper) {
var properties = value.getProperties();
var args = new Object[parameters.size()];
var i = 0;
for (var param : parameters) {
var property = properties.get(param.first);
if (property == null) {
var message =
String.format(
"Cannot convert Pkl object to Java object."
+ "%nPkl type : %s"
+ "%nJava type : %s"
+ "%nMissing Pkl property : %s"
+ "%nActual Pkl properties: %s",
value.getClassInfo(), targetType.getTypeName(), param.first, properties.keySet());
throw new ConversionException(message);
}
try {
var cachedPropertyType = cachedPropertyTypes[i];
if (!cachedPropertyType.isExactClassOf(property)) {
cachedPropertyType = PClassInfo.forValue(property);
cachedPropertyTypes[i] = cachedPropertyType;
cachedConverters[i] = valueMapper.getConverter(cachedPropertyType, param.second);
}
assert cachedConverters[i] != null;
args[i] = cachedConverters[i].convert(property, valueMapper);
i += 1;
} catch (ConversionException e) {
throw new ConversionException(
String.format(
"Error converting property `%s` in Pkl object of type `%s` "
+ "to equally named constructor parameter in Java class `%s`: "
+ e.getMessage(),
param.first,
value.getClassInfo(),
Reflection.toRawType(targetType).getTypeName()),
e.getCause());
}
}
try {
@SuppressWarnings("unchecked")
var result = (T) constructorHandle.invokeWithArguments(args);
return result;
} catch (Throwable t) {
throw new ConversionException(
String.format("Error invoking constructor `%s`.", constructorHandle), t);
}
}
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.reflect.*;
import java.util.*;
import org.pkl.core.PClassInfo;
import org.pkl.core.PObject;
public final class PObjectToMap implements ConverterFactory {
private final ConverterFactory pMapToMap = new PMapToMap();
@Override
public Optional<Converter<?, ?>> create(PClassInfo<?> sourceType, Type targetType) {
var targetClass = Reflection.toRawType(targetType);
if (!((sourceType == PClassInfo.Module || sourceType.getJavaClass() == PObject.class)
&& Map.class.isAssignableFrom(targetClass))) {
return Optional.empty();
}
@SuppressWarnings({"unchecked", "rawtypes"})
Optional<Converter<Map<?, ?>, Map<?, ?>>> underlying =
(Optional) pMapToMap.create(PClassInfo.Map, targetType);
return underlying.map(
converter ->
(Converter<PObject, Map<?, ?>>)
(value, valueMapper) -> converter.convert(value.getProperties(), valueMapper));
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.reflect.Type;
import java.util.Optional;
import org.pkl.core.PClassInfo;
import org.pkl.core.PObject;
final class PObjectToPObject implements ConverterFactory {
@Override
public Optional<Converter<?, ?>> create(PClassInfo<?> sourceType, Type targetType) {
if (!(sourceType.getJavaClass() == PObject.class
&& (targetType == Object.class || targetType == PObject.class))) {
return Optional.empty();
}
return Optional.of(Converter.identity());
}
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Optional;
import org.pkl.core.PClassInfo;
import org.pkl.core.Pair;
import org.pkl.core.util.Nullable;
final class PPairToPair implements ConverterFactory {
@Override
public Optional<Converter<?, ?>> create(PClassInfo<?> sourceType, Type targetType) {
if (sourceType != PClassInfo.Pair) return Optional.empty();
var targetClass = Reflection.toRawType(targetType);
if (!Pair.class.isAssignableFrom(targetClass)) {
return Optional.empty();
}
var pairType = (ParameterizedType) Reflection.getExactSupertype(targetType, Pair.class);
return Optional.of(
new ConverterImpl<>(
pairType.getActualTypeArguments()[0], pairType.getActualTypeArguments()[1]));
}
private static class ConverterImpl<F, S> implements Converter<Pair<Object, Object>, Pair<F, S>> {
private final Type firstTargetType;
private final Type secondTargetType;
private PClassInfo<Object> firstCachedType = PClassInfo.Unavailable;
private @Nullable Converter<Object, F> firstCachedConverter;
private PClassInfo<Object> secondCachedType = PClassInfo.Unavailable;
private @Nullable Converter<Object, S> secondCachedConverter;
public ConverterImpl(Type firstTargetType, Type secondTargetType) {
this.firstTargetType = firstTargetType;
this.secondTargetType = secondTargetType;
}
@Override
public Pair<F, S> convert(Pair<Object, Object> value, ValueMapper valueMapper) {
var first = value.getFirst();
if (!firstCachedType.isExactClassOf(first)) {
firstCachedType = PClassInfo.forValue(first);
firstCachedConverter = valueMapper.getConverter(firstCachedType, firstTargetType);
}
var second = value.getSecond();
if (!secondCachedType.isExactClassOf(second)) {
secondCachedType = PClassInfo.forValue(second);
secondCachedConverter = valueMapper.getConverter(secondCachedType, secondTargetType);
}
assert firstCachedConverter != null;
assert secondCachedConverter != null;
return new Pair<>(
firstCachedConverter.convert(first, valueMapper),
secondCachedConverter.convert(second, valueMapper));
}
}
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Optional;
import org.pkl.core.DataSizeUnit;
import org.pkl.core.DurationUnit;
import org.pkl.core.PClassInfo;
import org.pkl.core.util.CodeGeneratorUtils;
import org.pkl.core.util.CollectionUtils;
final class PStringToEnum implements ConverterFactory {
@Override
public Optional<Converter<?, ?>> create(PClassInfo<?> sourceType, Type targetType) {
var rawTargetType = Reflection.toRawType(targetType);
if (sourceType != PClassInfo.String || !rawTargetType.isEnum()) return Optional.empty();
return Optional.of(new ConverterImpl(rawTargetType));
}
private static final class ConverterImpl implements Converter<String, Enum<?>> {
private final Class<?> enumType;
private final Map<String, Enum<?>> enumValuesByName;
private ConverterImpl(Class<?> enumType) {
this.enumType = enumType;
var values = (Enum<?>[]) enumType.getEnumConstants();
enumValuesByName = CollectionUtils.newConcurrentHashMap(values.length);
// special-case: enums in the standard library have a different name compared to the Pkl
// string
if (enumType == DataSizeUnit.class) {
for (var value : values) {
var unit = (DataSizeUnit) value;
enumValuesByName.put(CodeGeneratorUtils.toEnumConstantName(unit.getSymbol()), value);
}
} else if (enumType == DurationUnit.class) {
for (var value : values) {
var unit = (DurationUnit) value;
enumValuesByName.put(CodeGeneratorUtils.toEnumConstantName(unit.getSymbol()), value);
}
} else {
for (Enum<?> value : values) {
enumValuesByName.put(value.name(), value);
}
}
}
@Override
public Enum<?> convert(String value, ValueMapper valueMapper) {
var enumValue = enumValuesByName.get(value);
if (enumValue == null) {
enumValue = enumValuesByName.get(CodeGeneratorUtils.toEnumConstantName(value));
if (enumValue != null) {
enumValuesByName.put(value, enumValue);
}
}
if (enumValue != null) {
return enumValue;
}
throw new ConversionException(
String.format(
"Cannot convert String `%s` to Enum value of type `%s`.",
value, enumType.getTypeName()));
}
}
}

View File

@@ -0,0 +1,162 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static java.util.Arrays.stream;
import io.leangen.geantyref.CaptureType;
import io.leangen.geantyref.GenericTypeReflector;
import java.lang.reflect.*;
import org.pkl.core.util.Nullable;
/**
* Reflection utilities for implementing {@link ConverterFactory}s. Mostly covers introspection of
* parameterized types, which is not covered by the {@code java.util.reflect} API.
*
* <p>The heavy lifting under the covers is done by the excellent ge(a)ntyref library.
*/
public final class Reflection {
private Reflection() {}
/**
* Returns the class with the given fully qualified name, or {@code null} if a class with the
* given name cannot be found.
*/
public static @Nullable Class<?> tryLoadClass(String qualifiedName) {
try {
// use Class.forName() rather than ClassLoader.loadClass()
// because Class.getClassLoader() is not supported by AOT
return Class.forName(qualifiedName);
} catch (ClassNotFoundException e) {
return null;
}
}
public static boolean isMissingTypeArguments(Type type) {
if (type instanceof WildcardType) {
var wildcardType = (WildcardType) type;
var baseType =
wildcardType.getLowerBounds().length > 0
? wildcardType.getLowerBounds()[0]
: wildcardType.getUpperBounds()[0];
return isMissingTypeArguments(baseType);
}
return GenericTypeReflector.isMissingTypeParameters(type);
}
/**
* Returns the normalized form of the given type. A normalized type is concrete (no wildcards) and
* instantiable (not an interface or abstract class).
*/
public static Type normalize(Type type) {
if (type instanceof WildcardType) {
var wcType = (WildcardType) type;
var bounds = wcType.getLowerBounds();
if (bounds.length > 0) return bounds[0];
bounds = wcType.getUpperBounds();
if (bounds.length > 0) return bounds[0];
}
return getExactSupertype(type, toRawType(type));
}
/**
* Returns the raw (erased) type for the given parameterized type, type bound for the given
* wildcard type, or the given type otherwise.
*/
public static Class<?> toRawType(Type type) {
return GenericTypeReflector.erase(type);
}
/**
* Returns the wrapper type for the given primitive type. If the given type is not a primitive
* type, returns the given type.
*/
@SuppressWarnings("unchecked")
// casts are safe as (say) boolean.class and Boolean.class are both of type Class<Boolean>
public static <T> Class<T> toWrapperType(Class<T> type) {
if (type == boolean.class) return (Class<T>) Boolean.class;
if (type == char.class) return (Class<T>) Character.class;
if (type == long.class) return (Class<T>) Long.class;
if (type == int.class) return (Class<T>) Integer.class;
if (type == short.class) return (Class<T>) Short.class;
if (type == byte.class) return (Class<T>) Byte.class;
if (type == double.class) return (Class<T>) Double.class;
if (type == float.class) return (Class<T>) Float.class;
return type;
}
/** Returns the (possibly parameterized) element type for the given array type. */
public static Type getArrayElementType(Type type) {
return GenericTypeReflector.getArrayComponentType(type);
}
/**
* Returns a parameterization of the given raw supertype, taking into account type arguments of
* the given subtype. For example, @{code getExactSupertype(listOf(String.class),
* Collection.class)} will return @{code collectionOf(String.class)}. If the given subtype is not
* a parameterized type, returns the given raw supertype. If the given types have no inheritance
* relationship, returns {@code null}.
*/
// call sites typically know that the given types have an inheritance relationship
// annotating the return type with @Nullable would lead to annoying IDE
// nullability warnings in these cases
public static Type getExactSupertype(Type type, Class<?> rawSupertype) {
return uncapture(GenericTypeReflector.getExactSuperType(type, rawSupertype));
}
/**
* Returns a parameterization of the given raw subtype, taking into account type arguments of the
* given supertype. For example, @{code getExactSubtype(collectionOf(String.class), List.class)}
* will return @{code listOf(String.class)}. If the given supertype is not a parameterized type,
* returns the given raw subtype. If the given types have no inheritance relationship, returns
* {@code null}.
*/
// call sites typically know that the given types have an inheritance relationship
// annotating the return type with @Nullable would lead to annoying IDE
// nullability warnings in these cases
public static Type getExactSubtype(Type type, Class<?> rawSubtype) {
return uncapture(GenericTypeReflector.getExactSubType(type, rawSubtype));
}
/**
* Returns the exact parameter types of the given method or constructor, taking into account type
* arguments of the given declaring type. For example, {@code
* getExactParameterTypes(List.class.getDeclaredMethod("get"), listOf(optionalOf(String.class)}
* will return {@code optionalOf(String.class)}. Throws {@link IllegalArgumentException} if the
* given method or constructor is not declared by the given type.
*/
public static Type[] getExactParameterTypes(Executable m, Type declaringType) {
return stream(
GenericTypeReflector.getExactParameterTypes(
m, GenericTypeReflector.annotate(declaringType)))
.map(annType -> uncapture(annType.getType()))
.toArray(Type[]::new);
}
/**
* Undoes the capture of a wildcard type, or returns the given type otherwise. Unlike wildcard
* types, capture types are not represented in the Java reflection API, but may be returned by
* geantyref's getExactXXX methods. This leads to problems, which is why our getExactXXX methods
* eliminate them.
*/
private static Type uncapture(Type type) {
if (type instanceof CaptureType) {
return ((CaptureType) type).getWildcardType();
}
return type;
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.util.Objects;
import org.pkl.core.util.Nullable;
// avoid name clash with org.pkl.core.Pair
final class Tuple2<S, T> {
final S first;
final T second;
private Tuple2(S first, T second) {
this.first = Objects.requireNonNull(first);
this.second = Objects.requireNonNull(second);
}
static <S, T> Tuple2<S, T> of(S first, T second) {
return new Tuple2<>(first, second);
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Tuple2)) return false;
var other = (Tuple2<?, ?>) obj;
return first.equals(other.first) && second.equals(other.second);
}
@Override
public int hashCode() {
return 31 * first.hashCode() + second.hashCode();
}
@Override
public String toString() {
return "Tuple2(" + first + ", " + second + ")";
}
}

View File

@@ -0,0 +1,78 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.reflect.Modifier;
import org.pkl.core.util.Nullable;
/**
* Maps a type requested during conversion to the implementation type to be instantiated. The
* requested type is often an interface type. The implementation type is always a class type. A
* typical example is mapping {@link java.util.List} to {@link java.util.ArrayList}.
*/
// kept simple for now
// if we wanted to have more sophisticated mappings, we could:
// * support mapping factories/strategies (cf. ConverterFactory/Converter)
// * support parameterized mappings (e.g. Set<MyEnum> -> EnumSet<MyEnum>)
public final class TypeMapping<S, T extends S> {
public final Class<S> requestedType;
public final Class<T> implementationType;
private TypeMapping(Class<S> requestedType, Class<T> implementationType) {
if (Modifier.isAbstract(implementationType.getModifiers())) {
throw new IllegalArgumentException(
String.format(
"`implementationType` must not be abstract, but `%s` is.",
implementationType.getTypeName()));
}
if (!requestedType.isAssignableFrom(implementationType)) {
throw new IllegalArgumentException(
String.format(
"`implementationType` must be assignable to `requestedType`, but `%s` is not assignable to `%s`.",
implementationType.getTypeName(), requestedType.getTypeName()));
}
if (requestedType.isArray() || implementationType.isArray()) {
throw new IllegalArgumentException("Type mappings are not supported for array types.");
}
this.requestedType = requestedType;
this.implementationType = implementationType;
}
public static <S, T extends S> TypeMapping<S, T> of(
Class<S> requestedType, Class<T> implementationType) {
return new TypeMapping<>(requestedType, implementationType);
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof TypeMapping)) return false;
var other = (TypeMapping<?, ?>) obj;
return requestedType == other.requestedType && implementationType == other.implementationType;
}
@Override
public int hashCode() {
return requestedType.hashCode() * 31 + implementationType.hashCode();
}
@Override
public String toString() {
return String.format(
"TypeMapping(%s, %s)", requestedType.getTypeName(), implementationType.getTypeName());
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.util.*;
/** Predefined type mappings that can be registered with {@link ValueMapperBuilder}. */
public final class TypeMappings {
private TypeMappings() {}
/** Type mappings for collection types. */
public static final Collection<TypeMapping<?, ?>> collections =
List.of(
TypeMapping.of(Iterable.class, ArrayList.class),
TypeMapping.of(Collection.class, ArrayList.class),
TypeMapping.of(List.class, ArrayList.class),
TypeMapping.of(Set.class, HashSet.class),
TypeMapping.of(Map.class, HashMap.class),
TypeMapping.of(SortedSet.class, TreeSet.class),
TypeMapping.of(SortedMap.class, TreeMap.class),
TypeMapping.of(Queue.class, ArrayDeque.class),
TypeMapping.of(Deque.class, ArrayDeque.class));
/** All type mappings defined in this class. */
public static final Collection<TypeMapping<?, ?>> all = collections;
}

View File

@@ -0,0 +1,100 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import io.leangen.geantyref.TypeArgumentNotInBoundException;
import io.leangen.geantyref.TypeFactory;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import org.pkl.core.Pair;
/**
* A factory for parameterized type literals such as {@code List<String>} or {@code MyClass<Foo,
* Bar>}. Used to express the desired target type in {@link ValueMapper#map(Object, Type)}.
*/
public final class Types {
private Types() {}
public static ParameterizedType parameterizedType(Class<?> rawType, Type... typeArguments) {
var typeParamsCount = rawType.getTypeParameters().length;
if (typeParamsCount == 0) {
throw new IllegalArgumentException(
String.format(
"Cannot parameterize `%s` because it does not have any type parameters.",
rawType.getTypeName()));
}
if (typeArguments.length != typeParamsCount) {
throw new IllegalArgumentException(
String.format(
"Expected %d type arguments for `%s`, but got %d.",
typeParamsCount, rawType.getTypeName(), typeArguments.length));
}
for (Type arg : typeArguments) {
if (arg instanceof Class) {
var clazz = (Class<?>) arg;
if (clazz.isPrimitive()) {
throw new IllegalArgumentException(
String.format(
"`%s.class` is not a valid type argument. Did you mean `%s.class`?",
clazz, Reflection.toWrapperType(clazz).getSimpleName()));
}
}
}
try {
return (ParameterizedType) TypeFactory.parameterizedClass(rawType, typeArguments);
} catch (TypeArgumentNotInBoundException e) {
throw new IllegalArgumentException(
String.format(
"Type argument `%s` for type parameter `%s` is not within bound `%s`.",
e.getArgument().getTypeName(),
e.getParameter().getTypeName(),
e.getBound().getTypeName()));
}
}
public static ParameterizedType optionalOf(Type elementType) {
return parameterizedType(Optional.class, elementType);
}
public static Type arrayOf(Type elementType) {
return TypeFactory.arrayOf(elementType);
}
public static ParameterizedType pairOf(Type firstType, Type secondType) {
return parameterizedType(Pair.class, firstType, secondType);
}
public static ParameterizedType iterableOf(Type elementType) {
return parameterizedType(Iterable.class, elementType);
}
public static ParameterizedType collectionOf(Type elementType) {
return parameterizedType(Collection.class, elementType);
}
public static ParameterizedType listOf(Type elementType) {
return parameterizedType(List.class, elementType);
}
public static ParameterizedType setOf(Type elementType) {
return parameterizedType(Set.class, elementType);
}
public static ParameterizedType mapOf(Type keyType, Type valueType) {
return parameterizedType(Map.class, keyType, valueType);
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.reflect.Type;
import org.pkl.core.PClassInfo;
import org.pkl.core.PModule;
/**
* Automatically converts Pkl objects to Java objects. Use {@link ValueMapperBuilder} to create an
* instance of this type, configured according to your needs.
*/
public interface ValueMapper {
/** Shorthand for {@code ValueMapperBuilder.preconfigured().build()}. */
static ValueMapper preconfigured() {
return ValueMapperBuilder.preconfigured().build();
}
/**
* Converts the given Pkl object to the given Java target type. The Pkl object can be an entire
* {@link PModule} or any value contained therein. See {@link PClassInfo#forValue} for which Java
* types are used to represent Pkl objects.
*
* <p>When mapping to a generic target type, a fully parameterized type needs to be passed, e.g.
* {@code List<String>}. Parameterized type literals can be created using {@link Types}, e.g.
* {@code Types.listOf(String.class)}.
*
* <p>If an error occurs during conversion, or if {@link ValueMapper} does not know how to convert
* from the given object to the given target type, a {@link ConversionException} is thrown.
*/
<S, T> T map(S model, Type targetType);
/**
* Same as {@link #map(Object, Type)}, except that the target type is narrowed from {@link Type}
* to {@link Class} to allow for better type inference.
*/
default <S, T> T map(S model, Class<T> targetType) {
return map(model, (Type) targetType);
}
/**
* Returns the converter with the given source and target types. Throws {@link
* ConversionException} if no such converter exists.
*/
<S, T> Converter<S, T> getConverter(PClassInfo<S> sourceType, Type targetType);
/**
* Same as {@link #getConverter(PClassInfo, Type)}, except that the target type is narrowed from
* {@link Type} to {@link Class} to allow for better type inference.
*/
default <S, T> Converter<S, T> getConverter(PClassInfo<S> sourceType, Class<T> targetType) {
return getConverter(sourceType, (Type) targetType);
}
/**
* Returns a value mapper builder that, unless further configured, will build value mappers with
* the same configuration as this value mapper. In other words, this is the inverse operation of
* {@link ValueMapperBuilder#build()}, except that a new builder is returned.
*/
ValueMapperBuilder toBuilder();
}

View File

@@ -0,0 +1,182 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Builds a {@link ValueMapper} configured with appropriate conversions, converter factories, and
* type mappings.
*/
@SuppressWarnings("UnusedReturnValue")
public final class ValueMapperBuilder {
private final List<Conversion<?, ?>> conversions = new ArrayList<>();
private final List<ConverterFactory> factories = new ArrayList<>();
private final List<TypeMapping<?, ?>> mappings = new ArrayList<>();
private ValueMapperBuilder() {}
/**
* Creates a builder without any preconfigured conversions, converter factories, or type mappings.
*
* @return a builder without any preconfigured conversions, converter factories, or type mappings
*/
public static ValueMapperBuilder unconfigured() {
return new ValueMapperBuilder();
}
/**
* Creates a builder preconfigured with all conversions, converter factories, and type mappings
* defined in this module.
*
* @return a builder preconfigured with all conversions, converter factories, and type mappings
* defined in this module
*/
public static ValueMapperBuilder preconfigured() {
return unconfigured()
.addConversions(Conversions.all)
.addConverterFactories(ConverterFactories.all)
.addTypeMappings(TypeMappings.all);
}
/**
* Adds the given conversion. For conversions to a primitive type, a conversion to the
* corresponding wrapper type is automatically added.
*
* @param conversion the conversion to be added
* @return this
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public ValueMapperBuilder addConversion(Conversion<?, ?> conversion) {
conversions.add(conversion);
if (conversion.targetType instanceof Class) {
var clazz = (Class<?>) conversion.targetType;
if (clazz.isPrimitive()) {
conversions.add(
Conversion.of(
conversion.sourceType,
Reflection.toWrapperType(clazz),
(Converter) conversion.converter));
}
}
return this;
}
/**
* Adds the given conversions. For conversions to a primitive type, a conversion to the
* corresponding wrapper type is automatically added.
*
* @param conversions the conversions to be added
* @return this
*/
public ValueMapperBuilder addConversions(Collection<Conversion<?, ?>> conversions) {
conversions.forEach(this::addConversion);
return this;
}
/** Removes any existing conversions, then adds the given conversions. */
public ValueMapperBuilder setConversions(Collection<Conversion<?, ?>> conversions) {
this.conversions.clear();
return addConversions(conversions);
}
/** Returns the currently set conversions. */
public List<Conversion<?, ?>> getConversions() {
return conversions;
}
/**
* Adds the given converter factory. Factories will be queried for converters in the same order as
* they are being added to this builder.
*
* @param factory the converter factory to be added
* @return this
*/
public ValueMapperBuilder addConverterFactory(ConverterFactory factory) {
factories.add(factory);
return this;
}
/**
* Adds the given converter factories. Factories will be queried for converters in the same order
* as they are being added to this builder.
*
* @param factories the converter factories to be added
* @return this
*/
public ValueMapperBuilder addConverterFactories(Collection<ConverterFactory> factories) {
factories.forEach(this::addConverterFactory);
return this;
}
/** Removes any existing converter factories, then adds the given factories. */
public ValueMapperBuilder setConverterFactories(Collection<ConverterFactory> factories) {
this.factories.clear();
return addConverterFactories(factories);
}
/** Returns the currently set converter factories. */
public List<ConverterFactory> getConverterFactories() {
return factories;
}
/**
* Adds the given type mapping.
*
* @param mapping the type mapping to be added
* @return this
*/
public ValueMapperBuilder addTypeMapping(TypeMapping<?, ?> mapping) {
mappings.add(mapping);
return this;
}
/**
* Adds the given type mappings.
*
* @param mappings the type mappings to be added
* @return this
*/
public ValueMapperBuilder addTypeMappings(Collection<TypeMapping<?, ?>> mappings) {
mappings.forEach(this::addTypeMapping);
return this;
}
/** Removes any existing type mappings, then adds the given mappings. */
public ValueMapperBuilder setTypeMappings(Collection<TypeMapping<?, ?>> mappings) {
this.mappings.clear();
return addTypeMappings(mappings);
}
/** Returns the currently set type mappings. */
public List<TypeMapping<?, ?>> getTypeMappings() {
return mappings;
}
/**
* Builds a mapper with the configured conversions, converter factories, and type mappings. If
* desired, the same builder can be used to build multiple mappers.
*
* @return a mapper with the configured conversions, converter factories, and type mappings
*/
public ValueMapper build() {
return new ValueMapperImpl(
// copy to shield against subsequent modification through builder
new ArrayList<>(conversions), new ArrayList<>(factories), new ArrayList<>(mappings));
}
}

View File

@@ -0,0 +1,127 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import org.pkl.core.PClassInfo;
import org.pkl.core.util.CollectionUtils;
class ValueMapperImpl implements ValueMapper {
private final Collection<Conversion<?, ?>> conversions;
private final Collection<ConverterFactory> factories;
private final Collection<TypeMapping<?, ?>> typeMappings;
private final Map<Tuple2<PClassInfo<?>, Type>, Converter<?, ?>> convertersMap;
private final Map<Class<?>, Class<?>> typeMappingsMap;
ValueMapperImpl(
Collection<Conversion<?, ?>> conversions,
Collection<ConverterFactory> factories,
Collection<TypeMapping<?, ?>> typeMappings) {
this.conversions = conversions;
this.factories = factories;
this.typeMappings = typeMappings;
convertersMap = CollectionUtils.newHashMap(conversions.size());
for (var conversion : conversions) {
convertersMap.put(
Tuple2.of(conversion.sourceType, conversion.targetType), conversion.converter);
}
this.typeMappingsMap = CollectionUtils.newHashMap(typeMappings.size());
for (var mapping : typeMappings) {
this.typeMappingsMap.put(mapping.requestedType, mapping.implementationType);
}
}
@Override
@SuppressWarnings("unchecked")
public <S, T> T map(S model, Type targetType) {
var sourceType = PClassInfo.forValue(model);
return (T) getConverter(sourceType, targetType).convert(model, this);
}
private <S> Class<?> getTargetType(PClassInfo<S> sourceType, Type targetType) {
var rawTargetType = Reflection.toRawType(targetType);
var determinedClass = ClassRegistry.get(sourceType);
if (determinedClass == null) {
return rawTargetType;
}
var rawRegisteredSchema = Reflection.toRawType(determinedClass);
if (rawTargetType.isAssignableFrom(rawRegisteredSchema)) {
return rawRegisteredSchema;
}
return rawTargetType;
}
@Override
public <S, T> Converter<S, T> getConverter(PClassInfo<S> sourceType, Type targetType) {
Tuple2<PClassInfo<?>, Type> key = Tuple2.of(sourceType, targetType);
@SuppressWarnings("unchecked")
Converter<S, T> converter = (Converter<S, T>) convertersMap.get(key);
if (converter != null) return converter;
if (Reflection.isMissingTypeArguments(targetType)) {
throw new IllegalArgumentException(
String.format("Target type `%s` is missing type arguments.", targetType));
}
Class<?> rawTargetType = getTargetType(sourceType, targetType);
@SuppressWarnings("unchecked")
var rawImplType = (Class<T>) typeMappingsMap.getOrDefault(rawTargetType, rawTargetType);
var implType = Reflection.getExactSubtype(targetType, rawImplType);
// look up implType converter
// because implType has wildcards removed, conversion to
// `? extends Number` or `? super Number` finds converter for `Number`
// (assuming `converters` has such a converter)
var implKey = Tuple2.of(sourceType, implType);
@SuppressWarnings("unchecked")
var implConverter = (Converter<S, T>) convertersMap.get(implKey);
if (implConverter != null) {
// TODO: give converter a chance to copy itself to avoid pollution of its inline caches
convertersMap.put(key, implConverter);
return implConverter;
}
// create implType converter
for (ConverterFactory factory : factories) {
@SuppressWarnings({"unchecked", "rawtypes"})
Optional<Converter<S, T>> newConverter = (Optional) factory.create(sourceType, implType);
if (newConverter.isPresent()) {
convertersMap.put(key, newConverter.get());
return newConverter.get();
}
}
throw new ConversionException(
String.format(
"Cannot convert `%s` to `%s` because no conversion was found.",
sourceType.getQualifiedName(), targetType.getTypeName()));
}
@Override
public ValueMapperBuilder toBuilder() {
return ValueMapperBuilder.unconfigured()
.setConversions(conversions)
.setConverterFactories(factories)
.setTypeMappings(typeMappings);
}
}

View File

@@ -0,0 +1,4 @@
@NonnullByDefault
package org.pkl.config.java.mapper;
import org.pkl.core.util.NonnullByDefault;

View File

@@ -0,0 +1,4 @@
@NonnullByDefault
package org.pkl.config.java;
import org.pkl.core.util.NonnullByDefault;

View File

@@ -0,0 +1,144 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.*;
import org.junit.jupiter.api.Test;
import org.pkl.config.java.mapper.ConverterFactories;
import org.pkl.core.SecurityManagers;
public class ConfigEvaluatorBuilderTest {
@Test
public void preconfiguredBuilderHasPreconfiguredUnderlyingBuilders() {
var builder = ConfigEvaluatorBuilder.preconfigured();
var evaluatorBuilder = builder.getEvaluatorBuilder();
assertThat(evaluatorBuilder).isNotNull();
assertThat(evaluatorBuilder.getEnvironmentVariables()).isEqualTo(System.getenv());
assertThat(evaluatorBuilder.getExternalProperties()).isEqualTo(System.getProperties());
var mapperBuilder = builder.getValueMapperBuilder();
assertThat(mapperBuilder).isNotNull();
assertThat(mapperBuilder.getConverterFactories()).isEqualTo(ConverterFactories.all);
}
@Test
public void unconfiguredBuilderHasUnconfiguredUnderlyingBuilders() {
var builder = ConfigEvaluatorBuilder.unconfigured();
var evaluatorBuilder = builder.getEvaluatorBuilder();
assertThat(evaluatorBuilder).isNotNull();
assertThat(evaluatorBuilder.getEnvironmentVariables()).isEmpty();
assertThat(evaluatorBuilder.getExternalProperties()).isEmpty();
var mapperBuilder = builder.getValueMapperBuilder();
assertThat(mapperBuilder).isNotNull();
assertThat(mapperBuilder.getConverterFactories()).isEmpty();
}
@Test
public void preconfiguredBuilderContainsProcessEnvironmentVariables() {
var builder = ConfigEvaluatorBuilder.preconfigured();
assertThat(builder.getEnvironmentVariables()).isEqualTo(System.getenv());
}
@Test
public void unconfiguredBuilderContainsNoEnvironmentVariables() {
var builder = ConfigEvaluatorBuilder.unconfigured();
assertThat(builder.getEnvironmentVariables()).isEmpty();
}
@Test
public void addEnvironmentVariables() {
var builder = ConfigEvaluatorBuilder.unconfigured();
builder.addEnvironmentVariable("ONE", "one");
var envVars = Map.of("TWO", "two", "THREE", "three");
builder.addEnvironmentVariables(envVars);
assertThat(builder.getEnvironmentVariables()).hasSize(3);
assertThat(builder.getEnvironmentVariables()).containsEntry("ONE", "one");
assertThat(builder.getEnvironmentVariables()).containsAllEntriesOf(envVars);
}
@Test
public void overrideEnvironmentVariables() {
var builder = ConfigEvaluatorBuilder.unconfigured();
var envVars1 = Map.of("TWO", "two", "THREE", "three");
builder.addEnvironmentVariables(envVars1);
var envVars2 = Map.of("FOUR", "four", "FIVE", "five");
builder.setEnvironmentVariables(envVars2);
assertThat(builder.getEnvironmentVariables()).hasSize(2);
assertThat(builder.getEnvironmentVariables()).containsAllEntriesOf(envVars2);
}
@Test
public void preconfiguredBuilderContainsSystemProperties() {
var builder = ConfigEvaluatorBuilder.preconfigured();
assertThat(builder.getExternalProperties()).isEqualTo(System.getProperties());
}
@Test
public void unconfiguredBuilderContainsNoExternalProperties() {
var builder = ConfigEvaluatorBuilder.unconfigured();
assertThat(builder.getExternalProperties()).isEmpty();
}
@Test
public void addExternalProperties() {
var builder = ConfigEvaluatorBuilder.unconfigured();
builder.addExternalProperty("ONE", "one");
var properties = Map.of("TWO", "two", "THREE", "three");
builder.addExternalProperties(properties);
assertThat(builder.getExternalProperties()).hasSize(3);
assertThat(builder.getExternalProperties()).containsEntry("ONE", "one");
assertThat(builder.getExternalProperties()).containsAllEntriesOf(properties);
}
@Test
public void overrideExternalProperties() {
var builder = ConfigEvaluatorBuilder.unconfigured();
var properties1 = Map.of("TWO", "two", "THREE", "three");
builder.addExternalProperties(properties1);
var properties2 = Map.of("FOUR", "four", "FIVE", "five");
builder.setExternalProperties(properties2);
assertThat(builder.getExternalProperties()).hasSize(2);
assertThat(builder.getExternalProperties()).containsAllEntriesOf(properties2);
}
@Test
public void setSecurityManager() {
var builder = ConfigEvaluatorBuilder.preconfigured();
assertThat(builder.getAllowedModules()).isEqualTo(SecurityManagers.defaultAllowedModules);
assertThat(builder.getAllowedResources()).isEqualTo(SecurityManagers.defaultAllowedResources);
var manager =
SecurityManagers.standard(List.of(), List.of(), SecurityManagers.defaultTrustLevels, null);
builder = ConfigEvaluatorBuilder.preconfigured().setSecurityManager(manager);
assertThat(builder.getSecurityManager()).isSameAs(manager);
}
}

View File

@@ -0,0 +1,179 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.text;
import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.pkl.config.java.mapper.Named;
import org.pkl.config.java.mapper.Types;
import org.pkl.core.PObject;
public class ConfigTest {
private final ConfigEvaluator evaluator = ConfigEvaluator.preconfigured();
private final Config pigeonConfig =
evaluator.evaluate(
text(
"pigeon { age = 30; friends = List(\"john\", \"mary\"); address { street = \"Fuzzy St.\" } }"));
private final Config pigeonModuleConfig =
evaluator.evaluate(
text("age = 30; friends = List(\"john\", \"mary\"); address { street = \"Fuzzy St.\" }"));
private final Config pairConfig =
evaluator.evaluate(text("x { first = \"file/path\"; second = 42 }"));
private final Config mapConfig = evaluator.evaluate(text("x = Map(\"one\", 1, \"two\", 2)"));
@Test
public void navigate() {
var pigeon = pigeonConfig.get("pigeon");
assertThat(pigeon.getQualifiedName()).isEqualTo("pigeon");
assertThat(pigeon.getRawValue()).isInstanceOf(PObject.class);
var address = pigeon.get("address");
assertThat(address.getQualifiedName()).isEqualTo("pigeon.address");
assertThat(address.getRawValue()).isInstanceOf(PObject.class);
var street = address.get("street");
assertThat(street.getQualifiedName()).isEqualTo("pigeon.address.street");
assertThat(street.getRawValue()).isInstanceOf(String.class);
assertThat(street.as(String.class)).isEqualTo("Fuzzy St.");
}
@Test
public void navigateToNonExistingObjectChild() {
var pigeon = pigeonConfig.get("pigeon");
var t = catchThrowable(() -> pigeon.get("non-existing"));
assertThat(t)
.isInstanceOf(NoSuchChildException.class)
.hasMessageStartingWith(
"Node `pigeon` of type `pkl.base#Dynamic` "
+ "does not have a property named `non-existing`.");
}
@Test
public void navigateToNonExistingMapChild() {
var map = mapConfig.get("x");
var t = catchThrowable(() -> map.get("non-existing"));
assertThat(t)
.isInstanceOf(NoSuchChildException.class)
.hasMessageStartingWith(
"Node `x` of type `pkl.base#Map` " + "does not have a key named `non-existing`.");
}
@Test
public void navigateToNonExistingLeafChild() {
var age = pigeonConfig.get("pigeon").get("age");
var t = catchThrowable(() -> age.get("non-existing"));
assertThat(t)
.isInstanceOf(NoSuchChildException.class)
.hasMessageStartingWith(
"Leaf node `pigeon.age` of type `pkl.base#Int` "
+ "does not have a child named `non-existing`.");
}
@Test
public void convertObjectToPojoByType() {
Person pigeon = pigeonConfig.get("pigeon").as(Person.class);
checkPigeon(pigeon);
}
@Test
public void convertObjectToPojoByJavaType() {
var pigeon = pigeonConfig.get("pigeon").as(JavaType.of(Person.class));
checkPigeon(pigeon);
}
@Test
public void convertModuleToPojoByType() {
var pigeon = pigeonModuleConfig.as(Person.class);
checkPigeon(pigeon);
}
@Test
public void convertModuleToPojoByJavaType() {
var pigeon = pigeonModuleConfig.as(JavaType.of(Person.class));
checkPigeon(pigeon);
}
private void checkPigeon(Person pigeon) {
assertThat(pigeon).isNotNull();
assertThat(pigeon.age).isEqualTo(30);
assertThat(pigeon.friends).containsExactly("john", "mary");
assertThat(pigeon.address.street).isEqualTo("Fuzzy St.");
}
@Test
public void convertToParameterizedTypeByType() {
Pair<Path, Integer> pair =
pairConfig.get("x").as(Types.parameterizedType(Pair.class, Path.class, Integer.class));
checkPair(pair);
}
@Test
public void convertToParameterizedTypeByJavaType() {
var pair = pairConfig.get("x").as(new JavaType<Pair<Path, Integer>>() {});
checkPair(pair);
}
private void checkPair(Pair<?, ?> pair) {
assertThat(pair).isNotNull();
assertThat(pair.first).isEqualTo(Path.of("file/path"));
assertThat(pair.second).isEqualTo(42);
}
public static class Person {
final int age;
final List<String> friends;
final Address address;
public Person(
@Named("age") int age,
@Named("friends") List<String> friends,
@Named("address") Address address) {
this.age = age;
this.friends = friends;
this.address = address;
}
}
public static class Address {
final String street;
public Address(@Named("street") String street) {
this.street = street;
}
}
public static class Pair<S, T> {
final S first;
final T second;
public Pair(@Named("first") S first, @Named("second") T second) {
this.first = first;
this.second = second;
}
}
}

View File

@@ -0,0 +1,128 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java;
import static org.assertj.core.api.Assertions.*;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.util.*;
import org.junit.jupiter.api.Test;
import org.pkl.config.java.mapper.Reflection;
import org.pkl.config.java.mapper.Types;
public class JavaTypeTest {
@Test
public void constructOptionalType() {
var type = JavaType.optionalOf(String.class);
assertThat(type).isEqualTo(new JavaType<Optional<String>>() {});
assertThat(type).isEqualTo(JavaType.optionalOf(JavaType.of(String.class)));
assertThat(Reflection.toRawType(type.getType())).isEqualTo(Optional.class);
}
@Test
public void constructArrayType() {
var type = JavaType.arrayOf(String.class);
assertThat(type).isEqualTo(new JavaType<String[]>() {});
assertThat(type).isEqualTo(JavaType.arrayOf(JavaType.of(String.class)));
assertThat(Reflection.toRawType(type.getType()).isArray()).isTrue();
}
@Test
public void constructIterableType() {
var type = JavaType.iterableOf(String.class);
assertThat(type).isEqualTo(new JavaType<Iterable<String>>() {});
assertThat(type).isEqualTo(JavaType.iterableOf(JavaType.of(String.class)));
assertThat(Reflection.toRawType(type.getType())).isEqualTo(Iterable.class);
}
@Test
public void constructCollectionType() {
var type = JavaType.collectionOf(String.class);
assertThat(type).isEqualTo(new JavaType<Collection<String>>() {});
assertThat(type).isEqualTo(JavaType.collectionOf(JavaType.of(String.class)));
assertThat(Reflection.toRawType(type.getType())).isEqualTo(Collection.class);
}
@Test
public void constructListType() {
var type = JavaType.listOf(String.class);
assertThat(type).isEqualTo(new JavaType<List<String>>() {});
assertThat(type).isEqualTo(JavaType.listOf(JavaType.of(String.class)));
assertThat(Reflection.toRawType(type.getType())).isEqualTo(List.class);
}
@Test
public void constructSetType() {
var type = JavaType.setOf(String.class);
assertThat(type).isEqualTo(new JavaType<Set<String>>() {});
assertThat(type).isEqualTo(JavaType.setOf(JavaType.of(String.class)));
assertThat(Reflection.toRawType(type.getType())).isEqualTo(Set.class);
}
@Test
public void constructMapType() {
var type = JavaType.mapOf(String.class, URI.class);
assertThat(type).isEqualTo(new JavaType<Map<String, URI>>() {});
assertThat(type).isEqualTo(JavaType.mapOf(JavaType.of(String.class), JavaType.of(URI.class)));
assertThat(Reflection.toRawType(type.getType())).isEqualTo(Map.class);
}
@Test
public void usageAsTypeToken() {
var javaType = new JavaType<Map<String, List<URI>>>() {};
assertThat(javaType.getType()).isEqualTo(Types.mapOf(String.class, Types.listOf(URI.class)));
}
@Test
public void sameTypesConstructedInDifferentWaysAreEqual() {
var type1 = JavaType.mapOf(JavaType.of(String.class), JavaType.listOf(URI.class));
var type2 = new JavaType<Map<String, List<URI>>>() {};
var type3 = JavaType.of(Types.mapOf(String.class, Types.listOf(URI.class)));
assertThat(type1).isEqualTo(type1);
assertThat(type2).isEqualTo(type1);
assertThat(type3).isEqualTo(type2);
assertThat(type2.hashCode()).isEqualTo(type1.hashCode());
assertThat(type3.hashCode()).isEqualTo(type2.hashCode());
}
@Test
public void differentTypesAreNotEqual() {
var type1 = JavaType.mapOf(JavaType.of(String.class), JavaType.listOf(URI.class));
var type2 = new JavaType<Map<String, List<URL>>>() {};
var type3 = JavaType.of(Types.mapOf(String.class, Types.listOf(Path.class)));
assertThat(type2).isNotEqualTo(type1);
assertThat(type3).isNotEqualTo(type1);
assertThat(type2).isNotEqualTo(type3);
// hopefully
assertThat(type2.hashCode()).isNotEqualTo(type1.hashCode());
assertThat(type3.hashCode()).isNotEqualTo(type1.hashCode());
assertThat(type3.hashCode()).isNotEqualTo(type2.hashCode());
}
@Test
public void sameStringRepresentationAsJavaLangReflectType() {
var type = JavaType.mapOf(String.class, URI.class);
assertThat(type.toString()).isEqualTo("java.util.Map<java.lang.String, java.net.URI>");
assertThat(type.toString()).isEqualTo(type.getType().toString());
}
}

View File

@@ -0,0 +1,73 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import java.io.File;
import java.nio.file.Path;
import java.time.temporal.ChronoUnit;
import java.util.regex.Pattern;
import org.junit.jupiter.api.Test;
import org.pkl.core.Duration;
import org.pkl.core.DurationUnit;
public class ConversionsTest {
@Test
public void pStringToFile() {
var file = Conversions.pStringToFile.converter.convert("relative/path", null);
assertThat(file).isEqualTo(new File("relative/path"));
var file2 = Conversions.pStringToFile.converter.convert("/absolute/path", null);
assertThat(file2).isEqualTo(new File("/absolute/path"));
var file3 = Conversions.pStringToFile.converter.convert("", null);
assertThat(file3).isEqualTo(new File(""));
}
@Test
public void pStringToPath() {
var path = Conversions.pStringToPath.converter.convert("relative/path", null);
assertThat(path).isEqualTo(Path.of("relative/path"));
var path2 = Conversions.pStringToPath.converter.convert("/absolute/path", null);
assertThat(path2).isEqualTo(Path.of("/absolute/path"));
var path3 = Conversions.pStringToPath.converter.convert("", null);
assertThat(path3).isEqualTo(Path.of(""));
}
@Test
public void pStringToPattern() {
var str = "(?i)\\w*";
var pattern = Conversions.pStringToPattern.converter.convert(str, null);
assertThat(pattern.pattern()).isEqualTo(str);
}
@Test
public void pRegexToString() {
var regex = Pattern.compile("(?i)\\w*");
var str = Conversions.pRegexToString.converter.convert(regex, null);
assertThat(str).isEqualTo("(?i)\\w*");
}
@Test
public void pDurationToDuration() {
var pDuration = new Duration(100, DurationUnit.MINUTES);
var duration = Conversions.pDurationToDuration.converter.convert(pDuration, null);
assertThat(duration).isEqualTo(java.time.Duration.of(100, ChronoUnit.MINUTES));
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.modulePath;
import java.util.*;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.pkl.core.Evaluator;
import org.pkl.core.PModule;
public class PAnyToOptionalTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PAnyToOptionalTest.pkl"));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
@AfterAll
public static void afterAll() {
evaluator.close();
}
@Test
public void ex1() {
var ex1 = module.getProperty("ex1");
Optional<String> mapped = mapper.map(ex1, Types.optionalOf(String.class));
assertThat(mapped).isEmpty();
}
@Test
public void ex2() {
var ex2 = module.getProperty("ex2");
Optional<String> mapped = mapper.map(ex2, Types.optionalOf(String.class));
assertThat(mapped).contains("str");
}
@Test
public void ex3() {
var ex3 = module.getProperty("ex3");
Optional<List<Integer>> mapped = mapper.map(ex3, Types.optionalOf(Types.listOf(Integer.class)));
assertThat(mapped).contains(List.of(1, 2, 3));
}
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.modulePath;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.pkl.core.Evaluator;
import org.pkl.core.PModule;
public class PCollectionToArrayTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PCollectionToArrayTest.pkl"));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
@AfterAll
public static void afterAll() {
evaluator.close();
}
@Test
public void ex1() {
var ex1 = module.getProperty("ex1");
assertThat(mapper.map(ex1, byte[].class)).isEqualTo(new byte[0]);
assertThat(mapper.map(ex1, short[].class)).isEqualTo(new short[0]);
assertThat(mapper.map(ex1, int[].class)).isEqualTo(new int[0]);
assertThat(mapper.map(ex1, long[].class)).isEqualTo(new long[0]);
}
@Test
public void ex2() {
var ex2 = module.getProperty("ex2");
assertThat(mapper.map(ex2, byte[].class)).isEqualTo(new byte[] {1, 2, 3});
assertThat(mapper.map(ex2, short[].class)).isEqualTo(new short[] {1, 2, 3});
assertThat(mapper.map(ex2, int[].class)).isEqualTo(new int[] {1, 2, 3});
assertThat(mapper.map(ex2, long[].class)).isEqualTo(new long[] {1, 2, 3});
}
@Test
public void ex3() {
var ex3 = module.getProperty("ex3");
assertThat(mapper.map(ex3, byte[].class)).isEqualTo(new byte[] {1, 2, 3});
assertThat(mapper.map(ex3, short[].class)).isEqualTo(new short[] {1, 2, 3});
assertThat(mapper.map(ex3, int[].class)).isEqualTo(new int[] {1, 2, 3});
assertThat(mapper.map(ex3, long[].class)).isEqualTo(new long[] {1, 2, 3});
}
@Test
public void ex4() {
var ex4 = module.getProperty("ex4");
assertThat(mapper.map(ex4, float[].class)).isEqualTo(new float[] {1f, 2f, 3.3f});
assertThat(mapper.map(ex4, double[].class)).isEqualTo(new double[] {1d, 2d, 3.3d});
}
@Test
public void ex5() {
var ex5 = module.getProperty("ex5");
assertThat(mapper.map(ex5, boolean[].class)).isEqualTo(new boolean[] {true, false, true});
}
@Test
public void ex6() {
var ex6 = module.getProperty("ex6");
assertThat(mapper.map(ex6, Person[].class))
.isEqualTo(new Person[] {new Person("pigeon", 40), new Person("parrot", 30)});
}
}

View File

@@ -0,0 +1,117 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.modulePath;
import java.util.List;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.pkl.core.Evaluator;
import org.pkl.core.PModule;
public class PCollectionToCollectionTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PCollectionToCollectionTest.pkl"));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
@AfterAll
public static void afterAll() {
evaluator.close();
}
@Test
public void ex1() {
var ex1 = module.getProperty("ex1");
List<Byte> mapped1 = mapper.map(ex1, Types.listOf(Byte.class));
assertThat(mapped1).isEmpty();
List<Short> mapped2 = mapper.map(ex1, Types.listOf(Short.class));
assertThat(mapped2).isEmpty();
List<Integer> mapped3 = mapper.map(ex1, Types.listOf(Integer.class));
assertThat(mapped3).isEmpty();
List<Long> mapped4 = mapper.map(ex1, Types.listOf(Long.class));
assertThat(mapped4).isEmpty();
}
@Test
public void ex2() {
var ex2 = module.getProperty("ex2");
List<Byte> mapped1 = mapper.map(ex2, Types.listOf(Byte.class));
assertThat(mapped1).containsExactly((byte) 1, (byte) 2, (byte) 3);
List<Short> mapped2 = mapper.map(ex2, Types.listOf(Short.class));
assertThat(mapped2).containsExactly((short) 1, (short) 2, (short) 3);
List<Integer> mapped3 = mapper.map(ex2, Types.listOf(Integer.class));
assertThat(mapped3).containsExactly(1, 2, 3);
List<Long> mapped4 = mapper.map(ex2, Types.listOf(Long.class));
assertThat(mapped4).containsExactly(1L, 2L, 3L);
}
@Test
public void ex3() {
var ex3 = module.getProperty("ex3");
List<Byte> mapped1 = mapper.map(ex3, Types.listOf(Byte.class));
assertThat(mapped1).containsExactly((byte) 1, (byte) 2, (byte) 3);
List<Short> mapped2 = mapper.map(ex3, Types.listOf(Short.class));
assertThat(mapped2).containsExactly((short) 1, (short) 2, (short) 3);
List<Integer> mapped3 = mapper.map(ex3, Types.listOf(Integer.class));
assertThat(mapped3).containsExactly(1, 2, 3);
List<Long> mapped4 = mapper.map(ex3, Types.listOf(Long.class));
assertThat(mapped4).containsExactly(1L, 2L, 3L);
}
@Test
public void ex4() {
var ex4 = module.getProperty("ex4");
List<Float> mapped1 = mapper.map(ex4, Types.listOf(Float.class));
assertThat(mapped1).containsExactly(1f, 2f, 3.3f);
List<Double> mapped2 = mapper.map(ex4, Types.listOf(Double.class));
assertThat(mapped2).containsExactly(1d, 2d, 3.3d);
}
@Test
public void ex5() {
var ex5 = module.getProperty("ex5");
List<Boolean> mapped = mapper.map(ex5, Types.listOf(Boolean.class));
assertThat(mapped).containsExactly(true, false, true);
}
@Test
public void ex6() {
var ex6 = module.getProperty("ex6");
List<Person> mapped = mapper.map(ex6, Types.listOf(Person.class));
Assertions.assertThat(mapped)
.containsExactly(new Person("pigeon", 40), new Person("parrot", 30));
}
}

View File

@@ -0,0 +1,115 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.modulePath;
import java.util.Map;
import java.util.Properties;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.pkl.core.*;
public class PMapToMapTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PMapToMapTest.pkl"));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
@AfterAll
public static void afterAll() {
evaluator.close();
}
@Test
public void ex1() {
var ex1 = module.getProperty("ex1");
Map<Integer, Integer> mapped = mapper.map(ex1, Types.mapOf(Integer.class, Integer.class));
assertThat(mapped).isEmpty();
}
@Test
public void ex2() {
var ex2 = module.getProperty("ex2");
Map<Integer, Integer> mapped = mapper.map(ex2, Types.mapOf(Integer.class, Integer.class));
assertThat(mapped).containsOnly(entry(1, 2), entry(2, 4), entry(3, 6));
Map<Byte, Double> mapped2 = mapper.map(ex2, Types.mapOf(Byte.class, Double.class));
assertThat(mapped2).containsOnly(entry((byte) 1, 2d), entry((byte) 2, 4d), entry((byte) 3, 6d));
}
@Test
public void ex3() {
var ex3 = module.getProperty("ex3");
Map<Integer, Double> mapped = mapper.map(ex3, Types.mapOf(Integer.class, Double.class));
assertThat(mapped).containsOnly(entry(1, 2d), entry(2, 4d), entry(3, 6.6d));
}
@Test
public void ex4() {
var ex4 = module.getProperty("ex4");
Map<String, Map<String, Object>> mapped =
mapper.map(ex4, Types.mapOf(String.class, Types.mapOf(String.class, Object.class)));
Map<String, Object> pigeon = Map.of("name", "pigeon", "age", 40L);
Map<String, Object> parrot = Map.of("name", "parrot", "age", 30L);
assertThat(mapped).containsOnly(entry("pigeon", pigeon), entry("parrot", parrot));
}
@Test
public void ex5() {
var ex5 = module.getProperty("ex5");
Map<String, Person> mapped = mapper.map(ex5, Types.mapOf(String.class, Person.class));
assertThat(mapped)
.containsOnly(
entry("pigeon", new Person("pigeon", 40)), entry("parrot", new Person("parrot", 30)));
}
@Test
public void ex6() {
var ex6 = module.getProperty("ex6");
Map<Person, String> mapped = mapper.map(ex6, Types.mapOf(Person.class, String.class));
assertThat(mapped)
.containsOnly(
entry(new Person("pigeon", 40), "pigeon"), entry(new Person("parrot", 30), "parrot"));
}
@Test
public void ex7() {
var mapper =
ValueMapperBuilder.preconfigured()
.addConversion(
Conversion.of(PClassInfo.Int, String.class, (num, mapper2) -> String.valueOf(num)))
.build();
var ex7 = module.getProperty("ex7");
// conversion from PInt to String kicks in because PMapToMap treats Properties as
// Map<String,String>
var properties = mapper.map(ex7, Properties.class);
assertThat(properties).hasSize(2);
assertThat(properties.getProperty("1")).isEqualTo("2");
assertThat(properties.getProperty("2")).isEqualTo("4");
}
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.modulePath;
import java.util.*;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.pkl.config.java.mapper.PObjectToDataObjectTest.Address;
import org.pkl.config.java.mapper.PObjectToDataObjectTest.Hobby;
import org.pkl.core.Evaluator;
import org.pkl.core.PModule;
public class PModuleToDataObjectTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PModuleToDataObjectTest.pkl"));
PObjectToDataObjectTest.Person pigeon =
new PObjectToDataObjectTest.Person(
"pigeon",
40,
EnumSet.of(Hobby.SURFING, Hobby.SWIMMING),
new Address("sesame street", 94105));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
@AfterAll
public static void afterAll() {
evaluator.close();
}
@Test
public void doit() {
assertThat(mapper.map(module, PObjectToDataObjectTest.Person.class)).isEqualTo(pigeon);
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.junit.jupiter.api.Test;
import org.pkl.core.PNull;
public class PNullToAnyTest {
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
@Test
public void test() {
// due to Conversions.identities
assertThat(mapper.map(PNull.getInstance(), PNull.class)).isEqualTo(PNull.getInstance());
assertThat(mapper.map(PNull.getInstance(), String.class)).isNull();
assertThat(mapper.map(PNull.getInstance(), Person.class)).isNull();
assertThat(mapper.map(PNull.getInstance(), Integer.class)).isNull();
assertThatThrownBy(() -> mapper.map(PNull.getInstance(), int.class))
.isInstanceOf(ConversionException.class);
}
public static class Person {}
}

View File

@@ -0,0 +1,162 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.modulePath;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
import javax.inject.Named;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.pkl.core.Evaluator;
import org.pkl.core.PModule;
import org.pkl.core.util.Nullable;
public class PObjectToDataObjectJavaxInjectTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PObjectToDataObjectTest.pkl"));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
private static final Person pigeon =
new Person(
"pigeon",
40,
EnumSet.of(Hobby.SURFING, Hobby.SWIMMING),
new Address("sesame street", 94105));
@AfterAll
public static void afterAll() {
evaluator.close();
}
@Test
public void ex1() {
var ex1 = module.getProperty("ex1");
assertThat(mapper.map(ex1, Person.class)).isEqualTo(pigeon);
}
@Test
public void ex2() {
var ex2 = module.getProperty("ex2");
assertThat(mapper.map(ex2, Person.class)).isEqualTo(pigeon);
}
@Test
public void ex3() {
var ex3 = module.getProperty("ex3");
Object mapped =
mapper.map(ex3, Types.parameterizedType(Pair.class, String.class, Integer.class));
assertThat(mapped).isEqualTo(new Pair<>("foo", 42));
}
static class Person {
final String name;
final int age;
final Set<Hobby> hobbies;
final Address address;
Person(
@Named("name") String name,
@Named("age") int age,
@Named("hobbies") Set<Hobby> hobbies,
@Named("address") Address address) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
this.address = address;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
var other = (Person) obj;
return name.equals(other.name)
&& age == other.age
&& hobbies.equals(other.hobbies)
&& address.equals(other.address);
}
@Override
public int hashCode() {
return Objects.hash(name, age, hobbies, address);
}
}
static class Address {
final String street;
final int zip;
Address(@Named("street") String street, @Named("zip") int zip) {
this.street = street;
this.zip = zip;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Address)) return false;
var other = (Address) obj;
return street.equals(other.street) && zip == other.zip;
}
@Override
public int hashCode() {
return Objects.hash(street, zip);
}
}
public enum Hobby {
SWIMMING,
SURFING,
READING
}
public static class Pair<S, T> {
public final S first;
public final T second;
public Pair(@Named("first") S first, @Named("second") T second) {
this.first = first;
this.second = second;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Pair)) return false;
var other = (Pair<?, ?>) obj;
return first.equals(other.first) && second.equals(other.second);
}
@Override
public int hashCode() {
return Objects.hash(first, second);
}
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.assertThat;
import com.example.OverriddenProperty;
import org.junit.jupiter.api.Test;
import org.pkl.config.java.ConfigEvaluator;
import org.pkl.core.ModuleSource;
class PObjectToDataObjectOverriddenPropertyTest {
@Test
void overriddenProperty() {
try (var evaluator = ConfigEvaluator.preconfigured()) {
var result =
evaluator
.evaluate(ModuleSource.modulePath("/codegenPkl/OverriddenProperty.pkl"))
.as(OverriddenProperty.class);
assertThat(result.theClass.bar.get(0).prop1).isEqualTo("hello");
assertThat(result.theClass.bar.get(0).prop2).isEqualTo("hello again");
}
}
}

View File

@@ -0,0 +1,255 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.modulePath;
import java.beans.ConstructorProperties;
import java.util.*;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.pkl.core.Evaluator;
import org.pkl.core.PModule;
import org.pkl.core.util.Nullable;
public class PObjectToDataObjectTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PObjectToDataObjectTest.pkl"));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
private static final Person pigeon =
new Person(
"pigeon",
40,
EnumSet.of(Hobby.SURFING, Hobby.SWIMMING),
new Address("sesame street", 94105));
private static final PersonConstructoProperties pigeon2 =
new PersonConstructoProperties(
"pigeon",
40,
EnumSet.of(Hobby.SURFING, Hobby.SWIMMING),
new Address("sesame street", 94105));
@AfterAll
public static void afterAll() {
evaluator.close();
}
@Test
public void ex1() {
var ex1 = module.getProperty("ex1");
assertThat(mapper.map(ex1, Person.class)).isEqualTo(pigeon);
}
@Test
public void ex1_constructor_properties() {
var ex1 = module.getProperty("ex1");
assertThat(mapper.map(ex1, PersonConstructoProperties.class)).isEqualTo(pigeon2);
}
@Test
public void ex2() {
var ex2 = module.getProperty("ex2");
assertThat(mapper.map(ex2, Person.class)).isEqualTo(pigeon);
}
@Test
public void ex3() {
var ex3 = module.getProperty("ex3");
Object mapped =
mapper.map(ex3, Types.parameterizedType(Pair.class, String.class, Integer.class));
assertThat(mapped).isEqualTo(new Pair<>("foo", 42));
}
@Test
public void ex4() {
var ex4 = module.getProperty("ex4");
var t = catchThrowable(() -> mapper.map(ex4, Address.class));
assertThat(t).isInstanceOf(ConversionException.class);
}
@Test
public void ex5() {
var ex5 = module.getProperty("ex5");
assertThat(mapper.map(ex5, UpperBounds.class).numbers).isEqualTo(List.of(1L, 2L, 3L));
assertThat(mapper.map(ex5, LowerBounds.class).numbers).isEqualTo(List.of(1, 2, 3));
}
@Test
public void errorMessageNamesPropertyWhoseConversionFailed() {
var ex3 = module.getProperty("ex3");
var t =
catchThrowable(
() ->
mapper.map(ex3, Types.parameterizedType(Pair.class, Integer.class, Integer.class)));
assertThat(t).isInstanceOf(ConversionException.class);
assertThat(t.getMessage())
.startsWith(
"Error converting property `first` in Pkl object of type `Dynamic` "
+ "to equally named constructor parameter in Java class "
+ "`org.pkl.config.java.mapper."
+ "PObjectToDataObjectTest$Pair`:");
}
static class Person {
final String name;
final int age;
final Set<Hobby> hobbies;
final Address address;
Person(
@Named("name") String name,
@Named("age") int age,
@Named("hobbies") Set<Hobby> hobbies,
@Named("address") Address address) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
this.address = address;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
var other = (Person) obj;
return name.equals(other.name)
&& age == other.age
&& hobbies.equals(other.hobbies)
&& address.equals(other.address);
}
@Override
public int hashCode() {
return Objects.hash(name, age, hobbies, address);
}
}
static class PersonConstructoProperties {
final String name;
final int age;
final Set<Hobby> hobbies;
final Address address;
@ConstructorProperties({"name", "age", "hobbies", "address"})
PersonConstructoProperties(String name, int age, Set<Hobby> hobbies, Address address) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
this.address = address;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof PersonConstructoProperties)) return false;
var other = (PersonConstructoProperties) obj;
return name.equals(other.name)
&& age == other.age
&& hobbies.equals(other.hobbies)
&& address.equals(other.address);
}
@Override
public int hashCode() {
return Objects.hash(name, age, hobbies, address);
}
}
static class Address {
final String street;
final int zip;
Address(@Named("street") String street, @Named("zip") int zip) {
this.street = street;
this.zip = zip;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Address)) return false;
var other = (Address) obj;
return street.equals(other.street) && zip == other.zip;
}
@Override
public int hashCode() {
return Objects.hash(street, zip);
}
}
enum Hobby {
SWIMMING,
SURFING,
READING
}
static class Pair<S, T> {
final S first;
final T second;
Pair(@Named("first") S first, @Named("second") T second) {
this.first = first;
this.second = second;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Pair)) return false;
var other = (Pair<?, ?>) obj;
return first.equals(other.first) && second.equals(other.second);
}
@Override
public int hashCode() {
return Objects.hash(first, second);
}
}
static class UpperBounds {
final List<? extends Number> numbers;
UpperBounds(@Named("numbers") List<? extends Number> numbers) {
this.numbers = numbers;
}
}
static class LowerBounds {
final List<? super Integer> numbers;
LowerBounds(@Named("numbers") List<? super Integer> numbers) {
this.numbers = numbers;
}
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.text;
import org.junit.jupiter.api.Test;
import org.pkl.config.java.ConfigEvaluator;
public class PObjectToInnerClassTest {
@SuppressWarnings("InnerClassMayBeStatic")
public class InnerConfig {
final String text;
public InnerConfig(@Named("text") String text) {
this.text = text;
}
}
// verify that a workaround for https://bugs.openjdk.java.net/browse/JDK-8025806 is in place
// conversion to inner class is still expected to fail but with the usual `ConversionException`
@Test
public void attemptToConvertToInnerClassDoesNotFailWithIndexOutOfBoundsException() {
try (var evaluator = ConfigEvaluator.preconfigured()) {
var config =
evaluator.evaluate(
text("class Inner {\n" + " text: String = \"Bar\"\n" + "}\n" + "inner: Inner"));
assertThatExceptionOfType(ConversionException.class)
.isThrownBy(() -> config.get("inner").as(InnerConfig.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.modulePath;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.pkl.core.*;
public class PObjectToPObjectTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PObjectToPObjectTest.pkl"));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
@AfterAll
public static void afterAll() {
evaluator.close();
}
private PObject pigeon;
private PObject parrot;
@BeforeEach
public void before() {
Map<String, Object> pigeonProps = Map.of("name", "pigeon", "age", 40L);
pigeon = new PObject(PClassInfo.Dynamic, pigeonProps);
Map<String, Object> parrotProps = Map.of("name", "parrot", "age", 30L);
parrot = new PObject(PClassInfo.Dynamic, parrotProps);
}
@Test
public void ex1() {
var ex1 = module.getProperty("ex1");
assertThat(mapper.map(ex1, PObject.class)).isEqualTo(pigeon);
}
@Test
public void ex2() {
var ex2 = module.getProperty("ex2");
List<PObject> mapped = mapper.map(ex2, Types.listOf(PObject.class));
assertThat(mapped).containsExactly(pigeon, parrot);
}
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.pkl.core.ModuleSource.modulePath;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.pkl.core.*;
public class PPairToPairTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PPairToPairTest.pkl"));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
@AfterAll
public static void afterAll() {
evaluator.close();
}
@Test
public void ex1() {
var ex1 = module.getProperty("ex1");
Pair<Integer, Duration> mapped = mapper.map(ex1, Types.pairOf(Integer.class, Duration.class));
assertThat(mapped).isEqualTo(new Pair<>(1, new Duration(3.0, DurationUnit.SECONDS)));
}
@Test
public void ex2() {
var ex2 = module.getProperty("ex2");
Pair<PObject, PObject> mapped = mapper.map(ex2, Types.pairOf(PObject.class, PObject.class));
assertThat(mapped.getFirst().getProperties())
.containsOnly(entry("name", "pigeon"), entry("age", 40L));
assertThat(mapped.getSecond().getProperties())
.containsOnly(entry("name", "parrot"), entry("age", 30L));
}
}

View File

@@ -0,0 +1,114 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.modulePath;
import java.util.List;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.pkl.core.DataSizeUnit;
import org.pkl.core.DurationUnit;
import org.pkl.core.Evaluator;
import org.pkl.core.PModule;
public class PStringToEnumTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PStringToEnumTest.pkl"));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
@AfterAll
public static void afterAll() {
evaluator.close();
}
public enum Hobby {
READING,
SWIMMING,
COUCH_SURFING
}
@Test
public void ex1() {
assertThat(mapper.map(module.getProperty("ex1"), Hobby.class)).isEqualTo(Hobby.COUCH_SURFING);
}
@Test
public void ex2() {
assertThat(mapper.map(module.getProperty("ex2"), Hobby.class)).isEqualTo(Hobby.COUCH_SURFING);
}
@Test
public void ex3() {
assertThat(mapper.map(module.getProperty("ex3"), Hobby.class)).isEqualTo(Hobby.COUCH_SURFING);
}
@Test
public void ex4() {
assertThat(mapper.map(module.getProperty("ex4"), Hobby.class)).isEqualTo(Hobby.COUCH_SURFING);
}
@Test
public void ex5() {
assertThat(mapper.map(module.getProperty("ex5"), Hobby.class)).isEqualTo(Hobby.COUCH_SURFING);
}
@Test
public void ex6() {
assertThat(mapper.map(module.getProperty("ex6"), Hobby.class)).isEqualTo(Hobby.COUCH_SURFING);
}
@Test
public void ex7() {
List<Hobby> mapped = mapper.map(module.getProperty("ex7"), Types.listOf(Hobby.class));
assertThat(mapped).containsExactly(Hobby.SWIMMING, Hobby.READING, Hobby.COUCH_SURFING);
}
@Test
public void ex8() {
List<Hobby> mapped = mapper.map(module.getProperty("ex8"), Types.listOf(Hobby.class));
assertThat(mapped).containsExactly(Hobby.COUCH_SURFING, Hobby.COUCH_SURFING);
}
@Test
public void ex9() {
var t = catchThrowable(() -> mapper.map(module.getProperty("ex9"), Types.listOf(Hobby.class)));
assertThat(t)
.isInstanceOf(ConversionException.class)
.hasMessage(
"Cannot convert String `other` to Enum value "
+ "of type `org.pkl.config.java.mapper.PStringToEnumTest$Hobby`.");
}
@Test
public void ex11() {
var unit = mapper.map(module.getProperty("ex11"), DurationUnit.class);
assertThat(unit).isEqualTo(DurationUnit.MINUTES);
}
@Test
public void ex12() {
var unit = mapper.map(module.getProperty("ex12"), DataSizeUnit.class);
assertThat(unit).isEqualTo(DataSizeUnit.GIGABYTES);
}
}

View File

@@ -0,0 +1,96 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.pkl.core.ModuleSource.modulePath;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.pkl.core.Evaluator;
import org.pkl.core.PModule;
import org.pkl.core.Version;
public class PStringToVersionTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PStringToVersionTest.pkl"));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
@AfterAll
public static void afterAll() {
evaluator.close();
}
@Test
public void ex1() {
var ex1 = module.get("ex1");
assert ex1 != null;
var mapped = mapper.map(ex1, Version.class);
assertThat(mapped).isEqualTo(Version.parse("1.2.3"));
}
@Test
public void ex2() {
var ex2 = module.get("ex2");
assert ex2 != null;
var mapped = mapper.map(ex2, Version.class);
assertThat(mapped).isEqualTo(Version.parse("1.2.3-rc.1"));
}
@Test
public void ex3() {
var ex3 = module.get("ex3");
assert ex3 != null;
var mapped = mapper.map(ex3, Version.class);
assertThat(mapped).isEqualTo(Version.parse("1.2.3+456.789"));
}
@Test
public void ex4() {
var ex4 = module.get("ex4");
assert ex4 != null;
var mapped = mapper.map(ex4, Version.class);
assertThat(mapped).isEqualTo(Version.parse("1.2.3-rc.1+456.789"));
}
@Test
public void ex5() {
var ex5 = module.get("ex5");
assertThatThrownBy(
() -> {
assert ex5 != null;
mapper.map(ex5, Version.class);
})
.isInstanceOf(ConversionException.class)
.hasCauseInstanceOf(IllegalArgumentException.class);
}
@Test
public void ex6() {
var ex6 = module.get("ex6");
assertThatThrownBy(
() -> {
assert ex6 != null;
mapper.map(ex6, Version.class);
})
.isInstanceOf(ConversionException.class)
.hasCauseInstanceOf(IllegalArgumentException.class);
}
}

View File

@@ -0,0 +1,78 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.pkl.core.ModuleSource.modulePath;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.pkl.core.Evaluator;
import org.pkl.core.PModule;
public class PVersionToStringTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PVersionToVersionTest.pkl"));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
@AfterAll
public static void afterAll() {
evaluator.close();
}
@Test
public void ex1() {
var ex1 = module.get("ex1");
assert ex1 != null;
var mapped = mapper.map(ex1, String.class);
assertThat(mapped).isEqualTo("1.2.3");
}
@Test
public void ex2() {
var ex2 = module.get("ex2");
assert ex2 != null;
var mapped = mapper.map(ex2, String.class);
assertThat(mapped).isEqualTo("1.2.3-rc.1");
}
@Test
public void ex3() {
var ex3 = module.get("ex3");
assert ex3 != null;
var mapped = mapper.map(ex3, String.class);
assertThat(mapped).isEqualTo("1.2.3+456.789");
}
@Test
public void ex4() {
var ex4 = module.get("ex4");
assert ex4 != null;
var mapped = mapper.map(ex4, String.class);
assertThat(mapped).isEqualTo("1.2.3-rc.1+456.789");
}
@Test
public void ex5() {
var ex5 = module.get("ex5");
assert ex5 != null;
var mapped = mapper.map(ex5, String.class);
assertThat(mapped).isEqualTo("999999999999999.0.0");
}
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.modulePath;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.pkl.core.*;
public class PVersionToVersionTest {
private static final Evaluator evaluator = Evaluator.preconfigured();
private static final PModule module =
evaluator.evaluate(modulePath("org/pkl/config/java/mapper/PVersionToVersionTest.pkl"));
private static final ValueMapper mapper = ValueMapperBuilder.preconfigured().build();
@AfterAll
public static void afterAll() {
evaluator.close();
}
@Test
public void ex1() {
var ex1 = module.get("ex1");
assert ex1 != null;
var mapped = mapper.map(ex1, Version.class);
assertThat(mapped).isEqualTo(Version.parse("1.2.3"));
}
@Test
public void ex2() {
var ex2 = module.get("ex2");
assert ex2 != null;
var mapped = mapper.map(ex2, Version.class);
assertThat(mapped).isEqualTo(Version.parse("1.2.3-rc.1"));
}
@Test
public void ex3() {
var ex3 = module.get("ex3");
assert ex3 != null;
var mapped = mapper.map(ex3, Version.class);
assertThat(mapped).isEqualTo(Version.parse("1.2.3+456.789"));
}
@Test
public void ex4() {
var ex4 = module.get("ex4");
assert ex4 != null;
var mapped = mapper.map(ex4, Version.class);
assertThat(mapped).isEqualTo(Version.parse("1.2.3-rc.1+456.789"));
}
@Test
public void ex5() {
var ex5 = module.get("ex5");
assertThatThrownBy(
() -> {
assert ex5 != null;
mapper.map(ex5, Version.class);
})
.isInstanceOf(ConversionException.class)
.hasCauseInstanceOf(ArithmeticException.class);
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import org.pkl.core.util.Nullable;
public class Person {
public final String name;
public final int age;
public Person(@Named("name") String name, @Named("age") int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
var other = (Person) obj;
return name.equals(other.name) && age == other.age;
}
@Override
public int hashCode() {
return name.hashCode() * 31 + Integer.hashCode(age);
}
}

View File

@@ -0,0 +1,22 @@
package org.pkl.config.java.mapper
import org.pkl.config.java.ConfigEvaluator
import org.pkl.core.ModuleSource
import com.example.Lib
import com.example.PolymorphicModuleTest
import com.example.PolymorphicModuleTest.Strudel
import com.example.PolymorphicModuleTest.TurkishDelight
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class PolymorphicTest {
@Test
fun `deserializing polymorphic objects`() {
val evaluator = ConfigEvaluator.preconfigured()
val module = evaluator.evaluate(ModuleSource.modulePath("/codegenPkl/PolymorphicModuleTest.pkl")).`as`(PolymorphicModuleTest::class.java)
assertThat(module.desserts[0]).isInstanceOf(Strudel::class.java)
assertThat(module.desserts[1]).isInstanceOf(TurkishDelight::class.java)
assertThat(module.planes[0]).isInstanceOf(Lib.Jet::class.java)
assertThat(module.planes[1]).isInstanceOf(Lib.Propeller::class.java)
}
}

View File

@@ -0,0 +1,97 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.junit.jupiter.api.Test;
public class ReflectionTest {
@SuppressWarnings("unused")
static class Container<T> {
Container(T element) {}
void setElement(T element) {}
}
static class Person {}
@Test
public void isMissingTypeArguments() {
assertThat(
Reflection.isMissingTypeArguments(
Types.parameterizedType(Container.class, Person.class)))
.isFalse();
assertThat(Reflection.isMissingTypeArguments(Container.class)).isTrue();
assertThat(Reflection.isMissingTypeArguments(Person.class)).isFalse();
}
@Test
public void toRawType() {
var type = Types.listOf(Person.class);
assertThat(Reflection.toRawType(type)).isEqualTo(List.class);
assertThat(Reflection.toRawType(List.class)).isEqualTo(List.class);
}
@Test
public void toWrapperType() {
assertThat(Reflection.toWrapperType(float.class)).isEqualTo(Float.class);
assertThat(Reflection.toWrapperType(Person.class)).isEqualTo(Person.class);
}
@Test
public void getArrayElementType() {
assertThat(Reflection.getArrayElementType(int[].class)).isEqualTo(int.class);
assertThat(Reflection.getArrayElementType(Person[].class)).isEqualTo(Person.class);
var containerOfPerson = Types.parameterizedType(Container.class, Person.class);
assertThat(Reflection.getArrayElementType(Types.arrayOf(containerOfPerson)))
.isEqualTo(containerOfPerson);
}
@Test
public void getExactSupertype() {
assertThat(
Reflection.getExactSupertype(
Types.parameterizedType(ArrayList.class, Person.class), Collection.class))
.isEqualTo(Types.parameterizedType(Collection.class, Person.class));
}
@Test
public void getExactSubtype() {
assertThat(
Reflection.getExactSubtype(
Types.parameterizedType(Collection.class, Person.class), ArrayList.class))
.isEqualTo(Types.parameterizedType(ArrayList.class, Person.class));
}
@Test
public void getExactParameterTypes() {
var type = Types.parameterizedType(Container.class, Person.class);
var ctor = Container.class.getDeclaredConstructors()[0];
assertThat(Reflection.getExactParameterTypes(ctor, type)).isEqualTo(new Type[] {Person.class});
var method = Container.class.getDeclaredMethods()[0];
assertThat(Reflection.getExactParameterTypes(method, type))
.isEqualTo(new Type[] {Person.class});
}
}

View File

@@ -0,0 +1,123 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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
*
* https://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.
*/
package org.pkl.config.java.mapper;
import static org.assertj.core.api.Assertions.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.*;
import org.junit.jupiter.api.Test;
public class TypesTest {
@Test
public void createParameterizedType() {}
@Test
public void createParameterizedTypeForClassWithoutTypeParameters() {
var t = catchThrowable(() -> Types.parameterizedType(String.class));
assertThat(t)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(
"Cannot parameterize `java.lang.String` "
+ "because it does not have any type parameters.");
}
@Test
public void createParameterizedTypeWithWrongNumberOfTypeArguments() {
var t =
catchThrowable(
() -> Types.parameterizedType(Map.class, Integer.class, String.class, URL.class));
assertThat(t)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Expected 2 type arguments for `java.util.Map`, but got 3.");
}
@Test
public void createParameterizedTypeWithPrimitiveTypeArgument() {
Throwable t = catchThrowable(() -> Types.parameterizedType(List.class, int.class));
assertThat(t)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("`int.class` is not a valid type argument. Did you mean `Integer.class`?");
}
@SuppressWarnings("unused")
static class Foo<T extends Bar> {}
static class Bar {}
@Test
public void createParameterizedTypeWithIncompatibleTypeArgument() {
Throwable t = catchThrowable(() -> Types.parameterizedType(Foo.class, String.class));
assertThat(t)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(
"Type argument `java.lang.String` for type parameter `T` is "
+ "not within bound `org.pkl.config.java.mapper.TypesTest$Bar`.");
}
@Test
public void createPrimitiveArrayType() {
assertThat(Types.arrayOf(int.class)).isEqualTo(int[].class);
}
static class Person {}
@Test
public void createObjectArrayType() {
assertThat(Types.arrayOf(Person.class)).isEqualTo(Person[].class);
}
@Test
public void createIterableType() {
ParameterizedType type = Types.iterableOf(Person.class);
assertThat(type.getRawType()).isEqualTo(Iterable.class);
assertThat(type.getActualTypeArguments()).isEqualTo(new Type[] {Person.class});
}
@Test
public void createCollectionType() {
ParameterizedType type = Types.collectionOf(Person.class);
assertThat(type.getRawType()).isEqualTo(Collection.class);
assertThat(type.getActualTypeArguments()).isEqualTo(new Type[] {Person.class});
}
@Test
public void createListType() {
ParameterizedType type = Types.listOf(Person.class);
assertThat(type.getRawType()).isEqualTo(List.class);
assertThat(type.getActualTypeArguments()).isEqualTo(new Type[] {Person.class});
}
@Test
public void createSetType() {
ParameterizedType type = Types.setOf(Person.class);
assertThat(type.getRawType()).isEqualTo(Set.class);
assertThat(type.getActualTypeArguments()).isEqualTo(new Type[] {Person.class});
}
@Test
public void createMapType() {
ParameterizedType type = Types.mapOf(String.class, Person.class);
assertThat(type.getRawType()).isEqualTo(Map.class);
assertThat(type.getActualTypeArguments()).isEqualTo(new Type[] {String.class, Person.class});
}
}

View File

@@ -0,0 +1,28 @@
module com.example.OverriddenProperty
abstract class BaseClass {
fixed bar: Listing<BaseBar> = new {
new {
prop1 = "hello"
}
}
}
theClass: TheClass
class TheClass extends BaseClass {
fixed bar: Listing<Bar> = new {
new {
prop1 = "hello"
prop2 = "hello again"
}
}
}
open class BaseBar {
prop1: String
}
class Bar extends BaseBar {
prop2: String
}

View File

@@ -0,0 +1,14 @@
module com.example.lib
open class Airplane {
name: String
numSeats: Int
}
class Jet extends Airplane {
isSuperSonic: Boolean
}
class Propeller extends Airplane {
isTurboprop: Boolean
}

View File

@@ -0,0 +1,24 @@
/// Gets generated into a Java via Gradle task `generateTestConfigClasses`.
module com.example.PolymorphicModuleTest
import "PolymorphicLib.pkl"
abstract class Dessert
class Strudel extends Dessert {
numberOfRolls: Int
}
class TurkishDelight extends Dessert {
isOfferedToEdmund: Boolean
}
desserts: Listing<Dessert> = new {
new Strudel { numberOfRolls = 3 }
new TurkishDelight { isOfferedToEdmund = true }
}
planes: Listing<PolymorphicLib.Airplane> = new {
new PolymorphicLib.Jet { name = "Concorde"; numSeats = 128; isSuperSonic = true }
new PolymorphicLib.Propeller { name = "Cessna 172"; numSeats = 4; isTurboprop = true }
}

View File

@@ -0,0 +1,4 @@
ex1 = null
ex2 = "str"
ex3 = List(1, 2, 3)

View File

@@ -0,0 +1,6 @@
ex1 = List()
ex2 = List(1, 2, 3)
ex3 = Set(1, 2, 3)
ex4 = List(1, 2, 3.3)
ex5 = List(true, false, true)
ex6 = List(new Dynamic { name = "pigeon"; age = 40 }, new Dynamic { name = "parrot"; age = 30 })

View File

@@ -0,0 +1,6 @@
ex1 = List()
ex2 = List(1, 2, 3)
ex3 = Set(1, 2, 3)
ex4 = List(1, 2, 3.3)
ex5 = List(true, false, true)
ex6 = List(new Dynamic { name = "pigeon"; age = 40 }, new Dynamic { name = "parrot"; age = 30 })

View File

@@ -0,0 +1,7 @@
ex1 = Map()
ex2 = Map(1, 2, 2, 4, 3, 6)
ex3 = Map(1, 2, 2, 4, 3, 6.6)
ex4 = Map("pigeon", Map("name", "pigeon", "age", 40), "parrot", Map("name", "parrot", "age", 30))
ex5 = Map("pigeon", new Dynamic { name = "pigeon"; age = 40 }, "parrot", new Dynamic { name = "parrot"; age = 30 })
ex6 = Map(new Dynamic { name = "pigeon"; age = 40 }, "pigeon", new Dynamic { name = "parrot"; age = 30 }, "parrot")
ex7 = Map(1, 2, 2, 4)

View File

@@ -0,0 +1,7 @@
name = "pigeon"
age = 40
hobbies = List("swimming", "surfing")
address {
street = "sesame street"
zip = 94105
}

View File

@@ -0,0 +1,14 @@
module org.pkl.test.PObjectToDataObject
abstract class Thing {
isThing: Boolean
}
class Person extends Thing {
name: String
}
thing: Thing = new Person {
name = "Bob"
isThing = true
}

View File

@@ -0,0 +1,45 @@
ex1 {
name = "pigeon"
age = 40
hobbies = List("swimming", "surfing")
address {
street = "sesame street"
zip = 94105
}
}
ex2 = new Person {
name = "pigeon"
age = 40
hobbies = List("swimming", "surfing")
address {
street = "sesame street"
zip = 94105
}
}
ex3 {
first = "foo"
second = 42
}
ex4 {
street = "sesame street"
zipp = 94105 // intentional typo
}
ex5 {
numbers = List(1, 2, 3)
}
class Person {
name: String
age: Int
hobbies: List<String>
address: Address
}
class Address {
street: String
zip: Int
}

View File

@@ -0,0 +1,2 @@
ex1 = new { name = "pigeon"; age = 40 }
ex2 = List(new Dynamic { name = "pigeon"; age = 40 }, new Dynamic { name = "parrot"; age = 30 })

View File

@@ -0,0 +1,7 @@
class Person {
name: String
age: Int
}
ex1 = Pair(1, 3.s)
ex2 = Pair(new Person { name = "pigeon"; age = 40 }, new Dynamic { name = "parrot"; age = 30 })

View File

@@ -0,0 +1,12 @@
ex1 = "couch surfing"
ex2 = "couch_surfing"
ex3 = "COUCH SURFING"
ex4 = "COUCH_SURFING"
ex5 = "couchSurfing"
ex6 = "couch Surfing"
ex7 = List("swimming", "reading", "couch surfing")
ex8 = List("couch surfing", "COUCH_SURFING")
ex9 = List("couch surfing", "other")
ex10 = "couchSurfing"
ex11 = "min"
ex12 = "gb"

View File

@@ -0,0 +1,8 @@
import "pkl:semver"
ex1 = "1.2.3"
ex2 = "1.2.3-rc.1"
ex3 = "1.2.3+456.789"
ex4 = "1.2.3-rc.1+456.789"
ex5 = "999999999999999.0.0"
ex6 = "not a version number"

View File

@@ -0,0 +1,7 @@
import "pkl:semver"
ex1 = semver.Version("1.2.3")
ex2 = semver.Version("1.2.3-rc.1")
ex3 = semver.Version("1.2.3+456.789")
ex4 = semver.Version("1.2.3-rc.1+456.789")
ex5 = semver.Version("999999999999999.0.0")