blob: f86bec0aecd14b0806cc0107508c1672d1c75091 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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
*
* http://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.apache.hugegraph.schema;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.HugeException;
import org.apache.hugegraph.HugeGraph;
import org.apache.hugegraph.exception.NotSupportException;
import org.apache.hugegraph.schema.builder.SchemaBuilder;
import org.apache.hugegraph.type.HugeType;
import org.apache.hugegraph.type.Propertiable;
import org.apache.hugegraph.type.define.AggregateType;
import org.apache.hugegraph.type.define.Cardinality;
import org.apache.hugegraph.type.define.DataType;
import org.apache.hugegraph.type.define.WriteType;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.LongEncoding;
public class PropertyKey extends SchemaElement implements Propertiable {
private DataType dataType;
private Cardinality cardinality;
private AggregateType aggregateType;
private WriteType writeType;
public PropertyKey(final HugeGraph graph, Id id, String name) {
super(graph, id, name);
this.dataType = DataType.TEXT;
this.cardinality = Cardinality.SINGLE;
this.aggregateType = AggregateType.NONE;
this.writeType = WriteType.OLTP;
}
@Override
public HugeType type() {
return HugeType.PROPERTY_KEY;
}
public DataType dataType() {
return this.dataType;
}
public void dataType(DataType dataType) {
this.dataType = dataType;
}
public Cardinality cardinality() {
return this.cardinality;
}
public void cardinality(Cardinality cardinality) {
this.cardinality = cardinality;
}
public AggregateType aggregateType() {
return this.aggregateType;
}
public void aggregateType(AggregateType aggregateType) {
this.aggregateType = aggregateType;
}
public void writeType(WriteType writeType) {
this.writeType = writeType;
}
public WriteType writeType() {
return this.writeType;
}
public boolean oltp() {
return this.writeType.oltp();
}
public boolean olap() {
return this.writeType.olap();
}
@Override
public Set<Id> properties() {
return Collections.emptySet();
}
public PropertyKey properties(Id... properties) {
if (properties.length > 0) {
throw new NotSupportException("PropertyKey.properties(Id)");
}
return this;
}
public void defineDefaultValue(Object value) {
// TODO add a field default_value
this.userdata().put(Userdata.DEFAULT_VALUE, value);
}
public Object defaultValue() {
// TODO add a field default_value
return this.userdata().get(Userdata.DEFAULT_VALUE);
}
public boolean hasSameContent(PropertyKey other) {
return super.hasSameContent(other) &&
this.dataType == other.dataType() &&
this.cardinality == other.cardinality() &&
this.aggregateType == other.aggregateType() &&
this.writeType == other.writeType();
}
public String clazz() {
String dataType = this.dataType().clazz().getSimpleName();
switch (this.cardinality) {
case SINGLE:
return dataType;
// A set of values: Set<DataType>
case SET:
return String.format("Set<%s>", dataType);
// A list of values: List<DataType>
case LIST:
return String.format("List<%s>", dataType);
default:
throw new AssertionError(String.format(
"Unsupported cardinality: '%s'", this.cardinality));
}
}
public Class<?> implementClazz() {
Class<?> cls;
switch (this.cardinality) {
case SINGLE:
cls = this.dataType().clazz();
break;
// A set of values: Set<DataType>
case SET:
cls = LinkedHashSet.class;
break;
// A list of values: List<DataType>
case LIST:
cls = ArrayList.class;
break;
default:
throw new AssertionError(String.format(
"Unsupported cardinality: '%s'", this.cardinality));
}
return cls;
}
@SuppressWarnings("unchecked")
public <T> T newValue() {
switch (this.cardinality) {
case SET:
return (T) new LinkedHashSet<>();
case LIST:
return (T) new ArrayList<>();
default:
// pass
break;
}
try {
return (T) this.implementClazz().newInstance();
} catch (Exception e) {
throw new HugeException("Failed to new instance of %s: %s",
this.implementClazz(), e.toString());
}
}
/**
* Check property value valid
* @param value the property value to be checked data type and cardinality
* @param <V> the property value class
* @return true if data type and cardinality satisfy requirements,
* otherwise false
*/
public <V> boolean checkValueType(V value) {
boolean valid;
switch (this.cardinality) {
case SINGLE:
valid = this.checkDataType(value);
break;
case SET:
valid = value instanceof Set;
valid = valid && this.checkDataType((Set<?>) value);
break;
case LIST:
valid = value instanceof List;
valid = valid && this.checkDataType((List<?>) value);
break;
default:
throw new AssertionError(String.format(
"Unsupported cardinality: '%s'", this.cardinality));
}
return valid;
}
/**
* Check type of the value valid
* @param value the property value to be checked data type
* @param <V> the property value original data type
* @return true if the value is or can convert to the data type,
* otherwise false
*/
private <V> boolean checkDataType(V value) {
return this.dataType().clazz().isInstance(value);
}
/**
* Check type of all the values(maybe some list properties) valid
* @param values the property values to be checked data type
* @param <V> the property value class
* @return true if all the values are or can convert to the data type,
* otherwise false
*/
private <V> boolean checkDataType(Collection<V> values) {
boolean valid = true;
for (Object o : values) {
if (!this.checkDataType(o)) {
valid = false;
break;
}
}
return valid;
}
public <V> Object serialValue(V value, boolean encodeNumber) {
V validValue = this.validValue(value);
E.checkArgument(validValue != null,
"Invalid property value '%s' for key '%s'",
value, this.name());
E.checkArgument(this.cardinality.single(),
"The cardinality can't be '%s' for navigation key '%s'",
this.cardinality, this.name());
if (this.dataType.isNumber() || this.dataType.isDate()) {
if (encodeNumber) {
return LongEncoding.encodeNumber(validValue);
} else {
return validValue.toString();
}
}
return validValue;
}
public <V> V validValueOrThrow(V value) {
V validValue = this.validValue(value);
if (validValue == null) {
E.checkArgument(false,
"Invalid property value '%s' for key '%s', " +
"expect a value of type %s, actual type %s",
value, this.name(), this.clazz(),
value.getClass().getSimpleName());
}
return validValue;
}
public <V> V validValue(V value) {
try {
return this.convValue(value);
} catch (RuntimeException e) {
throw new IllegalArgumentException(String.format(
"Invalid property value '%s' for key '%s': %s",
value, this.name(), e.getMessage()));
}
}
@SuppressWarnings("unchecked")
private <V, T> V convValue(V value) {
if (value == null) {
return null;
}
if (this.checkValueType(value)) {
// Same as expected type, no conversion required
return value;
}
V validValue = null;
Collection<T> validValues;
if (this.cardinality.single()) {
validValue = this.convSingleValue(value);
} else if (value instanceof Collection) {
assert this.cardinality.multiple();
Collection<T> collection = (Collection<T>) value;
if (value instanceof Set) {
validValues = new LinkedHashSet<>(collection.size());
} else {
assert value instanceof List;
validValues = new ArrayList<>(collection.size());
}
for (T element : collection) {
element = this.convSingleValue(element);
if (element == null) {
validValues = null;
break;
}
validValues.add(element);
}
validValue = (V) validValues;
} else {
assert this.cardinality.multiple();
E.checkArgument(false,
"Property value must be %s, but got '%s'(%s)",
this.cardinality, value,
value.getClass().getSimpleName());
}
return validValue;
}
private <V> V convSingleValue(V value) {
if (value == null) {
return null;
}
if (this.dataType().isNumber()) {
@SuppressWarnings("unchecked")
V number = (V) this.dataType().valueToNumber(value);
return number;
} else if (this.dataType().isDate()) {
@SuppressWarnings("unchecked")
V date = (V) this.dataType().valueToDate(value);
return date;
} else if (this.dataType().isUUID()) {
@SuppressWarnings("unchecked")
V uuid = (V) this.dataType().valueToUUID(value);
return uuid;
} else if (this.dataType().isBlob()) {
@SuppressWarnings("unchecked")
V blob = (V) this.dataType().valueToBlob(value);
return blob;
}
if (this.checkDataType(value)) {
return value;
}
return null;
}
public interface Builder extends SchemaBuilder<PropertyKey> {
TaskWithSchema createWithTask();
Builder asText();
Builder asInt();
Builder asDate();
Builder asUUID();
Builder asBoolean();
Builder asByte();
Builder asBlob();
Builder asDouble();
Builder asFloat();
Builder asLong();
Builder valueSingle();
Builder valueList();
Builder valueSet();
Builder calcMax();
Builder calcMin();
Builder calcSum();
Builder calcOld();
Builder calcSet();
Builder calcList();
Builder writeType(WriteType writeType);
Builder cardinality(Cardinality cardinality);
Builder dataType(DataType dataType);
Builder aggregateType(AggregateType aggregateType);
Builder userdata(String key, Object value);
Builder userdata(Map<String, Object> userdata);
}
}