package org.jetbrains.plugins.ruby.motion.symbols;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleServiceManager;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.ruby.motion.RubyMotionSymbolProvider;
import org.jetbrains.plugins.ruby.motion.RubyMotionUtil;
import org.jetbrains.plugins.ruby.motion.RubyMotionUtilImpl;
import org.jetbrains.plugins.ruby.motion.bridgesupport.Class;
import org.jetbrains.plugins.ruby.motion.bridgesupport.*;
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.Type;
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.fqn.FQN;
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.RTypedSyntheticSymbol;
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.Symbol;
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.Context;
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.CoreTypes;
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.RType;
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.RTypeFactory;
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.collections.RArrayType;
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.impl.REmptyType;
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.impl.RSymbolTypeImpl;
import java.util.*;
import java.util.concurrent.ExecutionException;
/**
* @author Dennis.Ushakov
*/
public class MotionSymbolUtil {
private static final Set<String> FLOAT_TYPES;
private static final Set<String> INT_TYPES;
static {
final HashSet<String> floatTypes = new HashSet<>();
Collections.addAll(floatTypes, "float", "double", "Float32", "Float64", "CGFloat");
FLOAT_TYPES = Collections.unmodifiableSet(floatTypes);
final HashSet<String> intTypes = new HashSet<>();
Collections.addAll(intTypes, "int", "short", "char", "long", "long long", "Byte", "SignedByte", "NSInteger", "NSUInteger", "size_t");
INT_TYPES = Collections.unmodifiableSet(intTypes);
}
public static RTypedSyntheticSymbol createFunctionSymbol(@NotNull final Module module,
@Nullable final MotionClassSymbol parent,
final Function function) {
return createFunctionSymbol(module, parent, function, getFunctionName(function));
}
public static List<RTypedSyntheticSymbol> createSelectorSymbols(@NotNull final Module module,
@Nullable final MotionClassSymbol parent,
final Function function) {
final List<RTypedSyntheticSymbol> result = new ArrayList<>();
for (String name : getSelectorNames(function)) {
result.add(createFunctionSymbol(module, parent, function, name));
}
return result;
}
static RTypedSyntheticSymbol createFunctionSymbol(final Module module,
@Nullable final MotionClassSymbol parent,
final Function function,
final String name) {
final RType rType = getTypeByName(module, function.getReturnValue());
return new FunctionSymbol(module, name, parent, rType, function);
}
public static RTypedSyntheticSymbol createConstantSymbol(final Module module,
final Constant constant) {
final String name = getConstantName(constant);
return new ConstantSymbol(module, constant, name, REmptyType.INSTANCE);
}
public static Symbol[] createStructFieldSymbols(final Module module,
Symbol parent,
Struct struct,
String name) {
final String typeName = struct.getFieldType(name);
final RType type = getTypeByName(module, typeName);
final RTypedSyntheticSymbol reader = new RTypedSyntheticSymbol(module.getProject(), name, Type.FIELD_READER, parent, type, 0);
final RTypedSyntheticSymbol writer = new RTypedSyntheticSymbol(module.getProject(), name + "=", Type.FIELD_WRITER, parent, type, 1);
return new Symbol[] {reader, writer};
}
private static String getConstantName(final Constant constant) {
return StringUtil.capitalize(constant.getName());
}
public static RType getTypeByName(Module module, final String typeName) {
return MotionTypeCache.getInstance(module).getType(typeName);
}
@NotNull
private static RType doGetTypeByName(@Nullable Module module, String typeName) {
if (module == null) {
return REmptyType.INSTANCE;
}
final Project project = module.getProject();
if (typeName.endsWith("*")) {
final RType type = doGetTypeByName(module, dereferencePointerType(typeName));
if (type != REmptyType.INSTANCE || "void".equals(dereferencePointerType(typeName))) {
return new RArrayType(project, type);
}
}
final RType primitiveType = getPrimitiveType(project, typeName);
if (primitiveType != null) {
return primitiveType;
}
final Collection<Framework> frameworks = ((RubyMotionUtilImpl)RubyMotionUtil.getInstance()).getFrameworks(module);
if (!typeName.endsWith("*")) {
final Symbol symbol = RubyMotionSymbolProvider.findClassOrStruct(module, frameworks, FQN.Builder.fromString(typeName).asList());
return symbol instanceof StructSymbol || (symbol != null && RubyMotionUtil.getInstance().isAndroid(module)) ?
new RSymbolTypeImpl(symbol, Context.INSTANCE) : REmptyType.INSTANCE;
}
typeName = dereferencePointerType(typeName);
final Symbol symbol = RubyMotionSymbolProvider.findClassOrStruct(module, frameworks, Collections.singletonList(typeName));
return symbol != null ? new RSymbolTypeImpl(symbol, Context.INSTANCE) : REmptyType.INSTANCE;
}
@NotNull
private static String dereferencePointerType(String typeName) {
return typeName.substring(0, typeName.length() - 1).trim();
}
@Nullable
private static RType getPrimitiveType(final Project project, final String typeName) {
if ("void".equals(typeName)) {
return REmptyType.INSTANCE;
}
if ("BOOL".equals(typeName) || "bool".equals(typeName)) {
return RTypeFactory.createBoolType(project);
}
if (FLOAT_TYPES.contains(typeName)) {
return RTypeFactory.createTypeByFQN(project, CoreTypes.Float);
}
if (INT_TYPES.contains(typeName) || INT_TYPES.contains(typeName.replace("unsigned ", "")) ||
typeName.matches("[SU]?Int\\d*") || typeName.matches("u?int\\d*_t")) {
return RTypeFactory.createTypeByFQN(project, CoreTypes.Fixnum);
}
return null;
}
private static String getFunctionName(Function function) {
final String name = function.getName();
final int index = name.indexOf(':');
return index >= 0 ? name.substring(0, index) : name;
}
public static List<String> getSelectorNames(Function function) {
final List<String> names = new ArrayList<>();
names.add(getFunctionName(function));
final String name = function.getName();
final int argsSize = function.getArguments().size();
getSelectorNames(names, name, argsSize);
return names;
}
public static void getSelectorNames(List<String> names, String name, int argsSize) {
if (argsSize == 2 && name.startsWith("set") && name.endsWith(":forKey:")) {
names.add("[]=");
} else if (argsSize == 1 && name.endsWith("ForKey:")) {
names.add("[]");
} else if (name.startsWith("is") && !name.endsWith(":")) {
names.add(StringUtil.decapitalize(name.substring(2)) + "?");
} else if (name.startsWith("set") && name.endsWith(":")) {
names.add(StringUtil.decapitalize(name.substring(3, name.length() - 1)) + "=");
}
}
public static class MotionTypeCache {
private final Module myModule;
private final LoadingCache<String, RType> myCache = CacheBuilder.newBuilder().initialCapacity(512).maximumSize(1024).
build(CacheLoader.from(new com.google.common.base.Function<String, RType>() {
@Override
public RType apply(@Nullable String typeName) {
return doGetTypeByName(myModule, typeName);
}
}));
public static MotionTypeCache getInstance(final Module module) {
return ModuleServiceManager.getService(module, MotionTypeCache.class);
}
public MotionTypeCache(final Module module) {
myModule = module;
}
public RType getType(final String typeName) {
try {
return myCache.get(typeName);
}
catch (ExecutionException ignored) { }
catch (UncheckedExecutionException e) {
final Throwable cause = e.getCause();
if (cause instanceof ProcessCanceledException) {
throw (ProcessCanceledException)cause;
}
}
return REmptyType.INSTANCE;
}
public void reset() {
myCache.invalidateAll();
}
}
public static class MotionSymbolsCache {
private final Module myModule;
private volatile Set<Symbol> mySymbols;
public static MotionSymbolsCache getInstance(final Module module) {
return ModuleServiceManager.getService(module, MotionSymbolsCache.class);
}
public MotionSymbolsCache(final Module module) {
myModule = module;
}
private void processMotionSymbols(final Set<Symbol> symbols) {
final Collection<Framework> frameworks = ((RubyMotionUtilImpl)RubyMotionUtil.getInstance()).getFrameworks(myModule);
processClasses(frameworks, symbols);
processStructs(frameworks, symbols);
processConstants(frameworks, symbols);
processFunctions(frameworks, symbols);
}
private void processStructs(Collection<Framework> frameworks, Set<Symbol> symbols) {
for (Framework framework : frameworks) {
for (Struct struct : framework.getStructs()) {
symbols.add(new StructSymbol(myModule, struct));
}
}
}
private void processFunctions(Collection<Framework> frameworks, Set<Symbol> symbols) {
for (Framework framework : frameworks) {
for (Function function : framework.getFunctions()) {
symbols.add(createFunctionSymbol(myModule, null, function));
}
for (Map.Entry<String, String> entry : framework.getFunctionAliases().entrySet()) {
final Function function = framework.getFunction(entry.getValue());
if (function != null) {
symbols.add(createFunctionSymbol(myModule, null, function, entry.getKey()));
}
}
}
}
private void processConstants(Collection<Framework> frameworks, Set<Symbol> symbols) {
for (Framework framework : frameworks) {
for (Constant constant : framework.getConstants()) {
symbols.add(createConstantSymbol(myModule, constant));
}
}
}
private void processClasses(Collection<Framework> frameworks, Set<Symbol> symbols) {
final MultiMap<String, Class> classesMap = new MultiMap<>();
for (Framework framework : frameworks) {
for (Class clazz : framework.getClasses()) {
classesMap.putValue(clazz.getName(), clazz);
}
}
for (Map.Entry<String, Collection<Class>> entry : classesMap.entrySet()) {
symbols.add(new MotionClassSymbol(myModule, (List<Class>)entry.getValue()));
}
}
public Set<Symbol> getSymbols() {
if (mySymbols == null) {
final Set<Symbol> symbols = new LinkedHashSet<>();
processMotionSymbols(symbols);
mySymbols = symbols;
}
return mySymbols;
}
public void reset() {
mySymbols = null;
}
}
}