blob: 49cd80706dbe428b36dc95ed306fc9690cfe39fa [file] [log] [blame]
/*
* Copyright 2000-2014 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 com.intellij.lang.properties.xml;
import com.intellij.ide.highlighter.XmlFileType;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.Consumer;
import com.intellij.util.indexing.*;
import com.intellij.util.io.DataExternalizer;
import com.intellij.util.io.EnumeratorStringDescriptor;
import com.intellij.util.io.IOUtil;
import com.intellij.util.io.KeyDescriptor;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.util.xml.NanoXmlUtil;
import net.n3.nanoxml.StdXMLReader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* @author Dmitry Avdeev
* Date: 7/25/11
*/
public class XmlPropertiesIndex extends FileBasedIndexExtension<XmlPropertiesIndex.Key, String>
implements FileBasedIndex.FileTypeSpecificInputFilter, DataIndexer<XmlPropertiesIndex.Key, String, FileContent>,
KeyDescriptor<XmlPropertiesIndex.Key> {
public final static Key MARKER_KEY = new Key();
public static final ID<Key,String> NAME = ID.create("xmlProperties");
private static final EnumeratorStringDescriptor ENUMERATOR_STRING_DESCRIPTOR = new EnumeratorStringDescriptor();
private static final String HTTP_JAVA_SUN_COM_DTD_PROPERTIES_DTD = "http://java.sun.com/dtd/properties.dtd";
@NotNull
@Override
public ID<Key, String> getName() {
return NAME;
}
@NotNull
@Override
public DataIndexer<Key, String, FileContent> getIndexer() {
return this;
}
@NotNull
@Override
public KeyDescriptor<Key> getKeyDescriptor() {
return this;
}
@NotNull
@Override
public DataExternalizer<String> getValueExternalizer() {
return ENUMERATOR_STRING_DESCRIPTOR;
}
@NotNull
@Override
public FileBasedIndex.InputFilter getInputFilter() {
return this;
}
@Override
public boolean dependsOnFileContent() {
return true;
}
@Override
public int getVersion() {
return 2;
}
@Override
public boolean acceptInput(@NotNull VirtualFile file) {
return true;
}
@Override
public void registerFileTypesUsedForIndexing(@NotNull Consumer<FileType> fileTypeSink) {
fileTypeSink.consume(XmlFileType.INSTANCE);
}
@NotNull
@Override
public Map<Key, String> map(@NotNull FileContent inputData) {
CharSequence text = inputData.getContentAsText();
if(CharArrayUtil.indexOf(text, HTTP_JAVA_SUN_COM_DTD_PROPERTIES_DTD, 0) == -1) {
return Collections.emptyMap();
}
MyIXMLBuilderAdapter builder = parse(text, false);
if (builder == null) return Collections.emptyMap();
HashMap<Key, String> map = builder.myMap;
if (builder.accepted) map.put(MARKER_KEY, "");
return map;
}
static boolean isPropertiesFile(XmlFile file) {
Project project = file.getProject();
if (DumbService.isDumb(project)) {
CharSequence contents = file.getViewProvider().getContents();
return CharArrayUtil.indexOf(contents, HTTP_JAVA_SUN_COM_DTD_PROPERTIES_DTD, 0) != -1 &&
isAccepted(contents);
}
return !FileBasedIndex.getInstance().processValues(NAME, MARKER_KEY, file.getVirtualFile(),
new FileBasedIndex.ValueProcessor<String>() {
@Override
public boolean process(VirtualFile file, String value) {
return false;
}
}, GlobalSearchScope.allScope(project));
}
private static boolean isAccepted(CharSequence bytes) {
MyIXMLBuilderAdapter builder = parse(bytes, true);
return builder != null && builder.accepted;
}
@Nullable
private static MyIXMLBuilderAdapter parse(CharSequence text, boolean stopIfAccepted) {
StdXMLReader reader = new StdXMLReader(CharArrayUtil.readerFromCharSequence(text)) {
@Override
public Reader openStream(String publicID, String systemID) throws IOException {
if (!HTTP_JAVA_SUN_COM_DTD_PROPERTIES_DTD.equals(systemID)) throw new IOException();
return new StringReader(" ");
}
};
MyIXMLBuilderAdapter builder = new MyIXMLBuilderAdapter(stopIfAccepted);
NanoXmlUtil.parse(reader, builder);
return builder;
}
@Override
public void save(@NotNull DataOutput out, Key value) throws IOException {
out.writeBoolean(value.isMarker);
if (value.key != null) {
IOUtil.writeUTF(out, value.key);
}
}
@Override
public Key read(@NotNull DataInput in) throws IOException {
boolean isMarker = in.readBoolean();
return isMarker ? MARKER_KEY : new Key(IOUtil.readUTF(in));
}
@Override
public int getHashCode(Key value) {
return value.hashCode();
}
@Override
public boolean isEqual(Key val1, Key val2) {
return val1.isMarker == val2.isMarker && Comparing.equal(val1.key, val2.key);
}
public static class Key {
final boolean isMarker;
final String key;
public Key(String key) {
this.key = key;
isMarker = false;
}
public Key() {
isMarker = true;
key = null;
}
@Override
public int hashCode() {
return isMarker ? 0 : key.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key1 = (Key)o;
if (isMarker != key1.isMarker) return false;
if (key != null ? !key.equals(key1.key) : key1.key != null) return false;
return true;
}
}
private static class MyIXMLBuilderAdapter extends NanoXmlUtil.IXMLBuilderAdapter {
boolean accepted;
boolean insideEntry;
String key;
private final HashMap<Key, String> myMap = new HashMap<Key, String>();
private final boolean myStopIfAccepted;
public MyIXMLBuilderAdapter(boolean stopIfAccepted) {
myStopIfAccepted = stopIfAccepted;
}
@Override
public void startElement(String name, String nsPrefix, String nsURI, String systemID, int lineNr)
throws Exception {
if (!accepted) {
if ("properties".equals(name)) {
accepted = true;
}
else throw NanoXmlUtil.ParserStoppedXmlException.INSTANCE;
}
else {
insideEntry = "entry".equals(name);
}
if (myStopIfAccepted) throw NanoXmlUtil.ParserStoppedXmlException.INSTANCE;
}
@Override
public void addAttribute(String key, String nsPrefix, String nsURI, String value, String type)
throws Exception {
if (insideEntry && "key".equals(key)) this.key = value;
}
@Override
public void addPCData(Reader reader, String systemID, int lineNr) throws Exception {
if (insideEntry && key != null) {
String value = StreamUtil.readTextFrom(reader);
myMap.put(new Key(key), value);
}
}
}
}