/*
* Copyright 2003-2016 JetBrains s.r.o.
*
* 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
*
* 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 jetbrains.mps.smodel;
import jetbrains.mps.classloading.ClassLoaderManager;
import jetbrains.mps.classloading.ModuleReloadListener;
import jetbrains.mps.components.CoreComponent;
import jetbrains.mps.kernel.model.SModelUtil;
import jetbrains.mps.module.ReloadableModule;
import jetbrains.mps.smodel.language.ConceptRegistryUtil;
import jetbrains.mps.smodel.legacy.ConceptMetaInfoConverter;
import jetbrains.mps.smodel.runtime.ConstraintsDescriptor;
import jetbrains.mps.smodel.runtime.PropertyConstraintsDescriptor;
import jetbrains.mps.util.Computable;
import jetbrains.mps.util.JavaNameUtil;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SDataType;
import org.jetbrains.mps.openapi.language.SPrimitiveDataType;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.model.SNode;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public abstract class PropertySupport {
private static final Logger LOG = LogManager.getLogger(PropertySupport.class);
private static PropertySupportCache ourPropertySupportCache;
/**
* new validation method
*/
@Deprecated
public boolean canSetValue(SNode node, String propertyName, String value, boolean nullsAlwaysAllowed) {
SProperty sprop = ((ConceptMetaInfoConverter) node.getConcept()).convertProperty(propertyName);
return canSetValue(node, sprop, value, nullsAlwaysAllowed);
}
public boolean canSetValue(SNode node, SProperty property, String value, boolean nullsAlwaysAllowed) {
if (value == null && nullsAlwaysAllowed) {
return true; // can always remove property
}
if (value == null) {
value = "";
}
if (!canSetValue(value)) {
return false;
}
PropertyConstraintsDescriptor descriptor = getPropertyConstraintsDescriptor(node, property);
if (descriptor == null) {
LOG.error("No property constraints are available for property " + property.getName() + " in node " + node.getPresentation());
return false;
}
return canSetValue(descriptor, node, property, value);
}
/*package*/
static PropertyConstraintsDescriptor getPropertyConstraintsDescriptor(SNode node, SProperty property) {
ConstraintsDescriptor constraintsDescriptor = ConceptRegistryUtil.getConstraintsDescriptor(node.getConcept());
PropertyConstraintsDescriptor descriptor;
descriptor = constraintsDescriptor.getProperty(property);
return descriptor;
}
@Deprecated
public boolean canSetValue(PropertyConstraintsDescriptor descriptor, SNode node, String propertyName, String value) {
if (value == null) {
value = "";
}
return descriptor.validateValue(node, value);
}
public boolean canSetValue(PropertyConstraintsDescriptor descriptor, SNode node, SProperty property, String value) {
if (value == null) {
value = "";
}
return descriptor.validateValue(node, value);
}
@Deprecated
public boolean canSetValue(SNode node, String propertyName, String value) {
return canSetValue(node, propertyName, value, true);
}
public boolean canSetValue(SNode node, SProperty property, String value) {
return canSetValue(node, property, value, true);
}
/**
* old validation method - keep it for compatibility
*/
protected abstract boolean canSetValue(String value);
public String toInternalValue(String value) {
return value;
}
public String fromInternalValue(String value) {
return value;
}
@NotNull
public static PropertySupport getPropertySupport(@NotNull final SProperty property) {
SDataType dataType = property.getType();
if (dataType != null) {
if (dataType instanceof SPrimitiveDataType) {
switch (((SPrimitiveDataType) dataType).getType()) {
case SPrimitiveDataType.BOOL:
return BooleanPropertySupport.INSTANCE;
case SPrimitiveDataType.INT:
return IntegerPropertySupport.INSTANCE;
case SPrimitiveDataType.STRING:
return DefaultPropertySupport.INSTANCE;
default:
throw new RuntimeException("Unknown primitive type: " + dataType);
}
} else {
//todo: get real prop support
SNode declarationNode = property.getDeclarationNode();
return declarationNode != null ? getPropertySupport(declarationNode) : DefaultPropertySupport.INSTANCE;
}
}
return DefaultPropertySupport.INSTANCE;
}
@NotNull
public static PropertySupport getPropertySupport(@NotNull final SNode propertyDeclaration) {
return NodeReadAccessCasterInEditor.runReadTransparentAction(new Computable<PropertySupport>() {
@Override
public PropertySupport compute() {
SNode dataType = SNodeUtil.getPropertyDeclaration_DataType(propertyDeclaration);
if (dataType != null) {
PropertySupport propertySupport = ourPropertySupportCache.get(dataType);
if (propertySupport != null) {
return propertySupport;
}
if (SNodeUtil.isInstanceOfPrimitiveDataTypeDeclaration(dataType)) {
String dataTypeName = dataType.getName();
if (Primitives.STRING_TYPE.equals(dataTypeName)) {
propertySupport = new DefaultPropertySupport();
} else if (Primitives.INTEGER_TYPE.equals(dataTypeName)) {
propertySupport = new IntegerPropertySupport();
} else if (Primitives.BOOLEAN_TYPE.equals(dataTypeName)) {
propertySupport = new BooleanPropertySupport();
} else {
throw new RuntimeException("Unknown primitive type: " + dataTypeName);
}
} else {
propertySupport = loadPropertySupport(propertyDeclaration);
}
if (propertySupport == null) {
propertySupport = new DefaultPropertySupport();
}
ourPropertySupportCache.put(dataType, propertySupport);
return propertySupport;
}
return new DefaultPropertySupport();
}
});
}
private static String getClassName(SNode propertyDataType) {
return propertyDataType.getName() + "_PropertySupport";
}
private static PropertySupport loadPropertySupport(SNode propertyDeclaration) {
SNode propertyDataType = SNodeUtil.getPropertyDeclaration_DataType(propertyDeclaration);
Language l = SModelUtil.getDeclaringLanguage(propertyDataType);
String propertySupportClassName = JavaNameUtil.fqClassName(propertyDataType.getModel(), getClassName(propertyDataType));
PropertySupport propertySupport = null;
try {
Class propertySupportClass = ClassLoaderManager.getInstance().getOwnClass(l, propertySupportClassName);
if (propertySupportClass != null) {
Constructor constructor = propertySupportClass.getConstructor();
propertySupport = (PropertySupport) constructor.newInstance();
} else {
LOG.error("Can't find a class " + propertySupportClassName + " using classloader of " + l.getModuleName() + " module");
}
} catch (NoSuchMethodException e) {
LOG.error(null, e);
} catch (InstantiationException e) {
LOG.error(null, e);
} catch (IllegalAccessException e) {
LOG.error(null, e);
} catch (InvocationTargetException e) {
LOG.error(null, e);
}
return propertySupport;
}
private static class DefaultPropertySupport extends PropertySupport {
public static DefaultPropertySupport INSTANCE = new DefaultPropertySupport();
@Override
public boolean canSetValue(String value) {
return true;
}
}
private static class IntegerPropertySupport extends PropertySupport {
public static IntegerPropertySupport INSTANCE = new IntegerPropertySupport();
@Override
public boolean canSetValue(String value) {
if (value != null && value.startsWith("+")) {
return false;
}
try {
Integer.parseInt(value);
return true;
} catch (NumberFormatException e) {
// it is OK
}
return false;
}
}
private static class BooleanPropertySupport extends PropertySupport {
public static BooleanPropertySupport INSTANCE = new BooleanPropertySupport();
@Override
public boolean canSetValue(String value) {
return String.valueOf(value).equals("true") || String.valueOf(value).equals("false");
}
@Override
public String fromInternalValue(String value) {
if ("true".equals(value)) {
return value;
}
return "false";
}
@Override
public String toInternalValue(String value) {
if ("true".equals(value)) {
return value;
}
return null;
}
}
/**
* TODO: remove in 3.3, replace with some generated code, probably in the StructureAspectDescriptor.
* Preserving the cache, but starting to listen to the reloading events to keep the cache up-to-date
*
* @deprecated
*/
@Deprecated
public static class PropertySupportCache implements CoreComponent, ModuleReloadListener {
private final Map<SNode, PropertySupport> myMap = new ConcurrentHashMap<SNode, PropertySupport>();
private final ClassLoaderManager myCLM;
public PropertySupportCache(ClassLoaderManager clm) {
myCLM = clm;
}
public void put(SNode key, PropertySupport cache) {
myMap.put(key, cache);
}
public PropertySupport get(SNode key) {
return myMap.get(key);
}
@Override
public void modulesReloaded(Set<ReloadableModule> modules) {
myMap.clear();
}
@Override
public void init() {
myCLM.addReloadListener(this);
PropertySupport.ourPropertySupportCache = this;
}
@Override
public void dispose() {
myCLM.removeReloadListener(this);
}
}
}