mirror of
https://github.com/apple/pkl.git
synced 2026-05-01 12:44:19 +02:00
Initial commit
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
179
pkl-config-java/src/main/java/org/pkl/config/java/JavaType.java
Normal file
179
pkl-config-java/src/main/java/org/pkl/config/java/JavaType.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NonnullByDefault
|
||||
package org.pkl.config.java.mapper;
|
||||
|
||||
import org.pkl.core.util.NonnullByDefault;
|
||||
@@ -0,0 +1,4 @@
|
||||
@NonnullByDefault
|
||||
package org.pkl.config.java;
|
||||
|
||||
import org.pkl.core.util.NonnullByDefault;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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)});
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
ex1 = null
|
||||
ex2 = "str"
|
||||
ex3 = List(1, 2, 3)
|
||||
|
||||
@@ -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 })
|
||||
@@ -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 })
|
||||
@@ -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)
|
||||
@@ -0,0 +1,7 @@
|
||||
name = "pigeon"
|
||||
age = 40
|
||||
hobbies = List("swimming", "surfing")
|
||||
address {
|
||||
street = "sesame street"
|
||||
zip = 94105
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
ex1 = new { name = "pigeon"; age = 40 }
|
||||
ex2 = List(new Dynamic { name = "pigeon"; age = 40 }, new Dynamic { name = "parrot"; age = 30 })
|
||||
@@ -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 })
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user