/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.util; import gw.plugin.ij.lang.psi.api.statements.IGosuUsesStatement; import gw.plugin.ij.lang.psi.api.statements.IGosuUsesStatementList; import com.google.common.collect.ImmutableSet; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiType; import com.jgoodies.common.base.Strings; import gw.lang.parser.IGosuParser; import gw.lang.parser.ITypeUsesMap; import gw.lang.reflect.IErrorType; import gw.lang.reflect.IType; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.gs.IGosuClass; import gw.plugin.ij.intentions.ImportClassHelper; import gw.plugin.ij.lang.psi.IGosuFileBase; import gw.plugin.ij.lang.psi.IGosuPsiElement; import gw.plugin.ij.lang.psi.impl.expressions.GosuTypeLiteralImpl; import org.jetbrains.annotations.NotNull; import java.util.*; import java.util.regex.Pattern; import static java.lang.Math.abs; import static java.util.Collections.sort; import static com.google.common.base.Strings.isNullOrEmpty; import static com.intellij.psi.util.ClassUtil.extractClassName; import static com.intellij.psi.util.ClassUtil.extractPackageName; import static com.intellij.psi.util.PsiTreeUtil.findChildOfType; import static com.intellij.psi.util.PsiTreeUtil.findChildrenOfType; import static gw.plugin.ij.completion.GosuClassNameInsertHandler.addImportForItem; import static gw.plugin.ij.intentions.ImportClassHelper.ResolveTypeResult.CONFLICT; import static gw.plugin.ij.lang.psi.util.GosuPsiParseUtil.parseProgramm; public class ClassLord { private static final Pattern PURGE_PATTERN = Pattern.compile("[\\(\\[<].*$"); public static String purgeClassName(String className) { return PURGE_PATTERN.matcher(className).replaceFirst("").trim(); } /** * Adds import for the type and for all generic declarations inside the type */ public static void importAllTypes(PsiType rootType, PsiFile file) { Set<PsiType> types = new LinkedHashSet<>(); collectAllTypes(rootType, types); for (PsiType type : types) { String qName = purgeClassName(type.getCanonicalText()); if (!hasImplicitImport(qName)) { ImportClassHelper importHelper = new ImportClassHelper(qName, (IGosuFileBase) file); importHelper.resolveType(); if (importHelper.resolveResult != CONFLICT) { addImportForItem(file, qName, extractClassName(qName)); } } } } public static boolean doImport(String fqn, PsiFile file) { return addImportForItem(file, fqn, extractClassName(fqn)); } public static void doImportAndStick(String fqn, PsiFile file) { if (doImport(fqn, file)) { stickImport(fqn, file); } } public static void stickImport(String className, PsiFile file) { IGosuUsesStatementList usesList = findChildOfType(file, IGosuUsesStatementList.class); if (usesList == null) { return; } List<String> imports = new ArrayList<>(); Map<String, IGosuUsesStatement> nameToStatement = new HashMap<>(); for (IGosuUsesStatement stmt : usesList.getUsesStatements()) { GosuTypeLiteralImpl typeLiteral = findChildOfType(stmt, GosuTypeLiteralImpl.class); if (typeLiteral == null) { continue; } String name = typeLiteral.getText(); if (name == null) { continue; } nameToStatement.put(name, stmt); imports.add(name); } if (imports.size() < 2) { return; } IGosuUsesStatement targetImport = nameToStatement.get(className); if (targetImport == null) { return; } sort(imports); int idx = imports.indexOf(className); if (idx < 0) { return; } final boolean BEFORE = true; final boolean AFTER = !BEFORE; String sibling; boolean addingType; int lastIdx = imports.size() - 1; if (idx == 0) { sibling = imports.get(1); addingType = BEFORE; } else if (idx == lastIdx) { sibling = imports.get(lastIdx - 1); addingType = AFTER; } else { String upper = imports.get(idx - 1); String lower = imports.get(idx + 1); int upperDistance = abs(className.compareTo(upper)); int lowerDistance = abs(className.compareTo(lower)); if (upperDistance < lowerDistance) { sibling = upper; addingType = AFTER; } else { sibling = lower; addingType = BEFORE; } } IGosuUsesStatement stmt = nameToStatement.get(sibling); PsiElement newTargetImport = targetImport.copy(); targetImport.delete(); if (BEFORE == addingType) { usesList.addBefore(newTargetImport, stmt); } else { usesList.addAfter(newTargetImport, stmt); } } /** * Collects all types which the type contains inside itself */ public static void collectAllTypes(PsiType type, Set<PsiType> types) { types.add(type); if (type instanceof PsiClassType) { for (PsiType generic : ((PsiClassType) type).getParameters()) { collectAllTypes(generic, types); } } } public static interface TypeSubstitutor { String substitute(GosuTypeLiteralImpl typeLiteral); } /** * returns list of [Set, List, String] for string "Set<List<String>>" */ public static List<String> flatten(String type, IGosuPsiElement context) { if (Strings.isBlank(type)) { return Collections.emptyList(); } PsiElement expr = parseProgramm("var x : " + type, context.getManager(), null); List<GosuTypeLiteralImpl> types = new ArrayList<>(findChildrenOfType(expr, GosuTypeLiteralImpl.class)); if (types.isEmpty()) { return Collections.emptyList(); } List<String> result = new ArrayList<>(); for (GosuTypeLiteralImpl typeLiteral : types) { result.add(typeLiteral.getType().getCanonicalText()); } return result; } public static String substitueTypes(String type, IGosuPsiElement context, TypeSubstitutor substitutor) { if (Strings.isBlank(type)) { return type; } PsiElement expr = parseProgramm("var x : " + type, context.getManager(), null); List<GosuTypeLiteralImpl> types = new ArrayList<>(findChildrenOfType(expr, GosuTypeLiteralImpl.class)); if (types.isEmpty()) { return ""; } GosuTypeLiteralImpl stubType = types.get(0); final int rootOffset = stubType.getTextOffset(); String result = stubType.getText(); int lastOffset = result.length(); ListIterator<GosuTypeLiteralImpl> it = types.listIterator(types.size()); while (it.hasPrevious()) { GosuTypeLiteralImpl typeLiteral = it.previous(); String typeText = purgeClassName(typeLiteral.getText()); int offset = typeLiteral.getTextOffset() - rootOffset; try { if (offset >= lastOffset) { continue; } } finally { lastOffset = offset; } String substitution = substitutor.substitute(typeLiteral); if (substitution != null && checkRange(result, 0, offset) && checkRange(result, offset + typeText.length(), result.length())) { result = result.substring(0, offset) + substitution + result.substring(offset + typeText.length(), result.length()); } } return result; } public static boolean checkRange(String value, int beginIndex, int endIndex) { if (beginIndex < 0) { return false; } if (endIndex > value.length()) { return false; } int subLen = endIndex - beginIndex; if (subLen < 0) { return false; } return true; } public static String reduceCollections(String type, IGosuPsiElement context) { final String[] SIMPLE_INTERFACES = {"java.util.List", "java.util.Set", "java.util.Map"}; return substitueTypes(type, context, new TypeSubstitutor() { public String substitute(GosuTypeLiteralImpl typeLiteral) { Set<String> inheritance = new HashSet<>(); extractInhertance(typeLiteral.getType(), inheritance); for (String iface : SIMPLE_INTERFACES) { if (inheritance.contains(iface)) { return iface; } } return null; } }); } public static String simplifyTypes(String type, final IGosuPsiElement context, final Map<String, String> simplified) { return simplifyTypes(type, context, false, simplified); } public static String simplifyTypes(String type, final IGosuPsiElement context, final boolean analizeFQNUsing, final Map<String, String> simplified) { return substitueTypes(type, context, new TypeSubstitutor() { public String substitute(GosuTypeLiteralImpl typeLiteral) { String typeText = purgeClassName(typeLiteral.getText()); int dotIdx = typeText.lastIndexOf('.'); if (dotIdx == -1) { return null; } ImportClassHelper importHelper = new ImportClassHelper(typeText, context); importHelper.resolveType(); if (importHelper.resolveResult == CONFLICT) { return null; } if (analizeFQNUsing) { importHelper.analizeFQNUsing(); if (importHelper.fqnIsPreferred()) { return null; } } String simpleName = extractClassName(typeText); String existing = simplified.get(simpleName); if (existing != null && !existing.equals(typeText)) { return null; } simplified.put(simpleName, typeText); return simpleName; } }); } private static void extractInhertance(PsiType type, Set<String> inheritance) { inheritance.add(purgeClassName(type.getCanonicalText())); for (PsiType t : type.getSuperTypes()) { extractInhertance(t, inheritance); } } private static final Set<String> GOSU_IMPLICIT_IMPORTS = ImmutableSet.of( "java.lang.String", "java.lang.Boolean", "java.lang.List", "java.lang.Number", "java.lang.Object"); public static boolean hasImplicitImport(String fqn) { return hasImplicitImport(fqn, TypeSystem.getDefaultTypeUsesMap()); } public static boolean hasImplicitImport(String fqn, ITypeUsesMap usesMap) { String namespace = extractPackageName(fqn); if (isNullOrEmpty(namespace)) { return true; } if ("gw.lang".equals(namespace)) { return true; } if (GOSU_IMPLICIT_IMPORTS.contains(fqn)) { return true; } IType resolved = usesMap.resolveType(fqn); if (resolved == null) { return false; } return usesMap.resolveType(resolved.getRelativeName()) == resolved; } public static boolean inUsesMap(String namespace, ITypeUsesMap usesMap) { return usesMap.containsType(namespace + "."); } public static boolean resolveRelativeTypeInParser(String strQName, String strRelativeName, @NotNull IGosuClass gosuClass) { IGosuParser parser = gosuClass.getParser(); if (parser != null) { TypeSystem.pushModule(gosuClass.getTypeLoader().getModule()); try { IType type = parser.resolveTypeLiteral(strRelativeName).getType().getType(); if (!(type instanceof IErrorType) && strQName.equals(type.getName())) { return true; } } finally { TypeSystem.popModule(gosuClass.getTypeLoader().getModule()); } } return false; } }