package com.intellij.javascript.flex.css; import com.intellij.javascript.flex.FlexAnnotationNames; import com.intellij.lang.javascript.JavaScriptSupportLoader; import com.intellij.lang.javascript.flex.FlexUtils; import com.intellij.lang.javascript.index.JSPackageIndex; import com.intellij.lang.javascript.psi.JSFile; import com.intellij.lang.javascript.psi.ecmal4.JSAttribute; import com.intellij.lang.javascript.psi.ecmal4.JSAttributeNameValuePair; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.javascript.psi.resolve.ActionScriptResolveUtil; import com.intellij.lang.javascript.psi.resolve.JSResolveUtil; import com.intellij.lang.javascript.psi.stubs.JSClassStub; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.stubs.PsiFileStub; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.stubs.StubTree; import com.intellij.psi.xml.XmlDocument; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.indexing.*; import com.intellij.util.io.*; import gnu.trove.THashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.Map; import java.util.Set; /** * @author Eugene.Kudelevsky */ public class FlexStyleIndex extends FileBasedIndexExtension<String, Set<FlexStyleIndexInfo>> { public static final ID<String, Set<FlexStyleIndexInfo>> INDEX_ID = ID.create("js.style.index"); private static final int VERSION = 18; private final DataExternalizer<Set<FlexStyleIndexInfo>> myDataExternalizer = new DataExternalizer<Set<FlexStyleIndexInfo>>() { @Override public void save(@NotNull DataOutput out, Set<FlexStyleIndexInfo> value) throws IOException { DataInputOutputUtil.writeINT(out, value.size()); for (FlexStyleIndexInfo info : value) { writeUTF(out, info.getClassOrFileName()); writeUTF(out, info.getAttributeName()); writeUTF(out, info.getInherit()); writeUTF(out, info.getType()); writeUTF(out, info.getArrayType()); writeUTF(out, info.getFormat()); writeUTF(out, info.getEnumeration()); out.writeBoolean(info.isInClass()); } } @Override public Set<FlexStyleIndexInfo> read(@NotNull DataInput in) throws IOException { int size = DataInputOutputUtil.readINT(in); Set<FlexStyleIndexInfo> result = ContainerUtil.newLinkedHashSet(); for (int i = 0; i < size; i++) { String className = readUTF(in); assert className != null; String attributeName = readUTF(in); assert attributeName != null; String inherit = readUTF(in); assert inherit != null; String type = readUTF(in); String arrayType = readUTF(in); String format = readUTF(in); String enumeration = readUTF(in); boolean inClass = in.readBoolean(); result.add(new FlexStyleIndexInfo(className, attributeName, inherit, type, arrayType, format, enumeration, inClass)); } return result; } }; @NotNull @Override public ID<String, Set<FlexStyleIndexInfo>> getName() { return INDEX_ID; } @Nullable private static String readUTF(@NotNull DataInput in) throws IOException { String s = IOUtil.readUTF(in); return s.length() == 0 ? null : s; } private static void writeUTF(@NotNull DataOutput out, @Nullable String s) throws IOException { IOUtil.writeUTF(out, s != null ? s : ""); } private static <TKey, TValue> void addElement(Map<TKey, Set<TValue>> map, TKey key, TValue value) { Set<TValue> list = map.get(key); if (list == null) { list = ContainerUtil.newLinkedHashSet(); map.put(key, list); } list.add(value); } @NotNull private static String getQualifiedNameByMxmlFile(@NotNull VirtualFile file, @NotNull Project project) { String name = FileUtil.getNameWithoutExtension(file.getName()); final String packageName = JSResolveUtil.getExpectedPackageNameFromFile(file, project); if (packageName != null && packageName.length() > 0) { return packageName + "." + name; } return name; } private static void indexMxmlFile(@NotNull final XmlFile file, @NotNull final VirtualFile virtualFile, @NotNull final Map<String, Set<FlexStyleIndexInfo>> map) { XmlTag rootTag = getRootTag(file); if (rootTag != null) { final String classQName = getQualifiedNameByMxmlFile(virtualFile, file.getProject()); final JSResolveUtil.JSInjectedFilesVisitor jsFilesVisitor = new JSResolveUtil.JSInjectedFilesVisitor() { @Override protected void process(JSFile file) { indexAttributes(file, classQName, true, map); } }; FlexUtils.processMxmlTags(rootTag, false, jsFilesVisitor); } } @Nullable private static XmlTag getRootTag(XmlFile file) { XmlDocument document = file.getDocument(); if (document != null) { return document.getRootTag(); } return null; } @NotNull @Override public DataIndexer<String, Set<FlexStyleIndexInfo>, FileContent> getIndexer() { return new DataIndexer<String, Set<FlexStyleIndexInfo>, FileContent>() { @Override @NotNull public Map<String, Set<FlexStyleIndexInfo>> map(@NotNull FileContent inputData) { final THashMap<String, Set<FlexStyleIndexInfo>> map = new THashMap<>(); if (JavaScriptSupportLoader.isFlexMxmFile(inputData.getFileName())) { PsiFile file = inputData.getPsiFile(); VirtualFile virtualFile = inputData.getFile(); if (file instanceof XmlFile) { indexMxmlFile((XmlFile)file, virtualFile, map); } } else { StubTree tree = JSPackageIndex.getStubTree(inputData); if (tree != null) { for (StubElement e : tree.getPlainList()) { if (e instanceof JSClassStub) { final PsiElement psiElement = e.getPsi(); if (psiElement instanceof JSClass) { final String qName = ((JSClass)psiElement).getQualifiedName(); indexAttributes(psiElement, qName, true, map); } } else if (e instanceof PsiFileStub) { PsiElement psiElement = e.getPsi(); if (psiElement instanceof JSFile) { String name = ((JSFile)psiElement).getName(); indexAttributes(psiElement, name, false, map); } } } } } return map; } }; } private static void indexAttributes(PsiElement element, final String classQName, final boolean inClass, final Map<String, Set<FlexStyleIndexInfo>> map) { ActionScriptResolveUtil.processMetaAttributesForClass(element, new ActionScriptResolveUtil.MetaDataProcessor() { @Override public boolean process(@NotNull JSAttribute jsAttribute) { String attrName = jsAttribute.getName(); if (attrName != null && FlexAnnotationNames.STYLE.equals(attrName)) { JSAttributeNameValuePair pair = jsAttribute.getValueByName("name"); String propertyName = pair != null ? pair.getSimpleValue() : null; if (propertyName != null) { if (classQName != null) { FlexStyleIndexInfo info = FlexStyleIndexInfo.create(classQName, propertyName, jsAttribute, inClass); if (info != null) { addElement(map, propertyName, info); String classicPropertyName = FlexCssUtil.toClassicForm(propertyName); if (!propertyName.equals(classicPropertyName)) { addElement(map, classicPropertyName, info); } } } } } return true; } @Override public boolean handleOtherElement(PsiElement el, PsiElement context, @Nullable Ref<PsiElement> continuePassElement) { return true; } }, false); } @NotNull @Override public KeyDescriptor<String> getKeyDescriptor() { return EnumeratorStringDescriptor.INSTANCE; } @NotNull @Override public DataExternalizer<Set<FlexStyleIndexInfo>> getValueExternalizer() { return myDataExternalizer; } @NotNull @Override public FileBasedIndex.InputFilter getInputFilter() { return FlexInputFilter.getInstance(); } @Override public boolean dependsOnFileContent() { return true; } @Override public int getVersion() { return VERSION; } }