/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.schema;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Provider;
import org.springframework.ide.eclipse.editor.support.hover.DescriptionProviders;
import org.springframework.ide.eclipse.editor.support.util.EnumValueParser;
import org.springframework.ide.eclipse.editor.support.util.HtmlSnippet;
import org.springframework.ide.eclipse.editor.support.util.ValueParser;
/**
* Static utility method for creating YType objects representing either
* 'array-like', 'map-like' or 'object-like' types which can be used
* to build up a 'Yaml Schema'.
*
* @author Kris De Volder
*/
public class YTypeFactory {
public YType yseq(YType el) {
return new YSeqType(el);
}
public YType ymap(YType key, YType val) {
return new YMapType(key, val);
}
public YBeanType ybean(String name, YTypedProperty... properties) {
return new YBeanType(name, properties);
}
/**
* YTypeUtil instances capable of 'interpreting' the YType objects created by this
* YTypeFactory
*/
public final YTypeUtil TYPE_UTIL = new YTypeUtil() {
@Override
public boolean isSequencable(YType type) {
return ((AbstractType)type).isSequenceable();
}
@Override
public boolean isMap(YType type) {
return ((AbstractType)type).isMap();
}
@Override
public boolean isAtomic(YType type) {
return ((AbstractType)type).isAtomic();
}
@Override
public Map<String, YTypedProperty> getPropertiesMap(YType type) {
return ((AbstractType)type).getPropertiesMap();
}
@Override
public List<YTypedProperty> getProperties(YType type) {
return ((AbstractType)type).getProperties();
}
@Override
public YValueHint[] getHintValues(YType type) {
return ((AbstractType)type).getHintValues();
}
@Override
public YType getDomainType(YType type) {
return ((AbstractType)type).getDomainType();
}
@Override
public String niceTypeName(YType type) {
return type.toString();
}
@Override
public YType getKeyType(YType type) {
return ((AbstractType)type).getKeyType();
}
@Override
public boolean isBean(YType type) {
return ((AbstractType)type).isBean();
}
@Override
public ValueParser getValueParser(YType type) {
return ((AbstractType)type).getParser();
}
};
/////////////////////////////////////////////////////////////////////////////////////
/**
* Provides default implementations for all YType methods.
*/
public static abstract class AbstractType implements YType {
private ValueParser parser;
private List<YTypedProperty> propertyList = new ArrayList<>();
private final List<YValueHint> hints = new ArrayList<>();
private Map<String, YTypedProperty> cachedPropertyMap;
private Provider<Collection<YValueHint>> hintProvider;
public boolean isSequenceable() {
return false;
}
public boolean isBean() {
return false;
}
public YType getKeyType() {
return null;
}
public YType getDomainType() {
return null;
}
public void addHintProvider(Provider<Collection<YValueHint>> hintProvider) {
this.hintProvider = hintProvider;
}
public YValueHint[] getHintValues() {
Collection<YValueHint> providerHints = hintProvider != null ? hintProvider.get() : null;
if (providerHints == null || providerHints.isEmpty()) {
return hints.toArray(new YValueHint[hints.size()]);
} else {
// Only merge if there are provider hints to merge
Set<YValueHint> mergedHints = new LinkedHashSet<>();
// Add type hints first
for (YValueHint val : hints) {
mergedHints.add(val);
}
// merge the provider hints
for (YValueHint val : providerHints) {
mergedHints.add(val);
}
return mergedHints.toArray(new YValueHint[mergedHints.size()]);
}
}
public final List<YTypedProperty> getProperties() {
return Collections.unmodifiableList(propertyList);
}
public final Map<String, YTypedProperty> getPropertiesMap() {
if (cachedPropertyMap==null) {
cachedPropertyMap = new LinkedHashMap<>();
for (YTypedProperty p : propertyList) {
cachedPropertyMap.put(p.getName(), p);
}
}
return Collections.unmodifiableMap(cachedPropertyMap);
}
public boolean isAtomic() {
return false;
}
public boolean isMap() {
return false;
}
public abstract String toString(); // force each sublcass to implement a (nice) toString method.
public void addProperty(YTypedProperty p) {
cachedPropertyMap = null;
propertyList.add(p);
}
public void addProperty(String name, YType type, Provider<HtmlSnippet> description) {
YTypedPropertyImpl prop;
addProperty(prop = new YTypedPropertyImpl(name, type));
prop.setDescriptionProvider(description);
}
public void addProperty(String name, YType type) {
addProperty(new YTypedPropertyImpl(name, type));
}
public void addHints(String... strings) {
if (strings != null) {
for (String value : strings) {
BasicYValueHint hint = new BasicYValueHint(value);
if (!hints.contains(hint)) {
hints.add(hint);
}
}
}
}
public void parseWith(ValueParser parser) {
this.parser = parser;
}
public ValueParser getParser() {
return parser;
}
}
public static class YMapType extends AbstractType {
private final YType key;
private final YType val;
private YMapType(YType key, YType val) {
this.key = key;
this.val = val;
}
@Override
public String toString() {
return "Map<"+key.toString()+", "+val.toString()+">";
}
@Override
public boolean isMap() {
return true;
}
@Override
public YType getKeyType() {
return key;
}
@Override
public YType getDomainType() {
return val;
}
}
public static class YSeqType extends AbstractType {
private YType el;
private YSeqType(YType el) {
this.el = el;
}
@Override
public String toString() {
return el.toString()+"[]";
}
@Override
public boolean isSequenceable() {
return true;
}
@Override
public YType getDomainType() {
return el;
}
}
public static class YBeanType extends AbstractType {
private final String name;
public YBeanType(String name, YTypedProperty[] properties) {
this.name = name;
for (YTypedProperty p : properties) {
addProperty(p);
}
}
@Override
public String toString() {
return name;
}
public boolean isBean() {
return true;
}
}
public static class YAtomicType extends AbstractType {
private final String name;
private YAtomicType(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
@Override
public boolean isAtomic() {
return true;
}
}
public static class YTypedPropertyImpl implements YTypedProperty {
final private String name;
final private YType type;
private Provider<HtmlSnippet> descriptionProvider = DescriptionProviders.NO_DESCRIPTION;
private YTypedPropertyImpl(String name, YType type) {
this.name = name;
this.type = type;
}
@Override
public String getName() {
return this.name;
}
@Override
public YType getType() {
return this.type;
}
@Override
public String toString() {
return name + ":" + type;
}
@Override
public HtmlSnippet getDescription() {
return descriptionProvider.get();
}
public void setDescriptionProvider(Provider<HtmlSnippet> descriptionProvider) {
this.descriptionProvider = descriptionProvider;
}
}
public YAtomicType yatomic(String name) {
return new YAtomicType(name);
}
public YTypedPropertyImpl yprop(String name, YType type) {
return new YTypedPropertyImpl(name, type);
}
public YAtomicType yenum(String name, String... values) {
YAtomicType t = yatomic(name);
t.addHints(values);
t.parseWith(new EnumValueParser(name, values));
return t;
}
}