2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.lang.properties.psi.impl;
18 import com.intellij.extapi.psi.PsiFileBase;
19 import com.intellij.lang.ASTFactory;
20 import com.intellij.lang.ASTNode;
21 import com.intellij.lang.properties.*;
22 import com.intellij.lang.properties.ResourceBundle;
23 import com.intellij.lang.properties.parsing.PropertiesElementTypes;
24 import com.intellij.lang.properties.psi.PropertiesElementFactory;
25 import com.intellij.lang.properties.psi.PropertiesFile;
26 import com.intellij.lang.properties.psi.Property;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.intellij.openapi.fileTypes.FileType;
29 import com.intellij.psi.FileViewProvider;
30 import com.intellij.psi.PsiElement;
31 import com.intellij.psi.TokenType;
32 import com.intellij.psi.impl.source.tree.ChangeUtil;
33 import com.intellij.psi.impl.source.tree.TreeElement;
34 import com.intellij.psi.stubs.PsiFileStub;
35 import com.intellij.psi.stubs.StubElement;
36 import com.intellij.psi.stubs.StubTree;
37 import com.intellij.psi.tree.TokenSet;
38 import com.intellij.util.ArrayUtil;
39 import com.intellij.util.IncorrectOperationException;
40 import com.intellij.util.containers.ContainerUtil;
41 import com.intellij.util.containers.MostlySingularMultiMap;
42 import gnu.trove.THashMap;
43 import org.jetbrains.annotations.NonNls;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
48 import java.util.stream.Collectors;
50 public class PropertiesFileImpl extends PsiFileBase implements PropertiesFile {
51 private static final Logger LOG = Logger.getInstance(PropertiesFileImpl.class);
52 private static final TokenSet PROPERTIES_LIST_SET = TokenSet.create(PropertiesElementTypes.PROPERTIES_LIST);
53 private volatile MostlySingularMultiMap<String,IProperty> myPropertiesMap; //guarded by lock
54 private volatile List<IProperty> myProperties; //guarded by lock
55 private volatile boolean myAlphaSorted;
56 private final Object lock = new Object();
58 public PropertiesFileImpl(FileViewProvider viewProvider) {
59 super(viewProvider, PropertiesLanguage.INSTANCE);
64 public FileType getFileType() {
65 return PropertiesFileType.INSTANCE;
69 public String toString() {
70 return "Properties file:" + getName();
75 public List<IProperty> getProperties() {
76 ensurePropertiesLoaded();
80 private ASTNode getPropertiesList() {
81 return ArrayUtil.getFirstElement(getNode().getChildren(PROPERTIES_LIST_SET));
84 private void ensurePropertiesLoaded() {
85 if (myPropertiesMap != null) return;
86 final StubTree stubTree = getStubTree();
87 List<IProperty> properties;
88 MostlySingularMultiMap<String, IProperty> propertiesMap = new MostlySingularMultiMap<String, IProperty>();
89 if (stubTree != null) {
90 final PsiFileStub root = stubTree.getRoot();
91 final StubElement propertiesList = root.findChildStubByType(PropertiesElementTypes.PROPERTIES_LIST);
92 if (propertiesList != null) {
93 properties = Arrays.stream(propertiesList.getChildrenByType(PropertiesElementTypes.PROPERTY, Property[]::new))
94 .map(IProperty.class::cast)
95 .peek(p -> propertiesMap.add(p.getKey(), p))
96 .collect(Collectors.toList());
98 properties = Collections.emptyList();
101 final ASTNode[] props = getPropertiesList().getChildren(PropertiesElementTypes.PROPERTIES);
102 properties = new ArrayList<IProperty>(props.length);
103 for (final ASTNode prop : props) {
104 final Property property = (Property)prop.getPsi();
105 String key = property.getUnescapedKey();
106 propertiesMap.add(key, property);
107 properties.add(property);
110 final boolean isAlphaSorted = PropertiesImplUtil.isAlphaSorted(properties);
111 synchronized (lock) {
112 if (myPropertiesMap != null) return;
113 myProperties = properties;
114 myPropertiesMap = propertiesMap;
115 myAlphaSorted = isAlphaSorted;
120 public IProperty findPropertyByKey(@NotNull String key) {
121 ensurePropertiesLoaded();
122 synchronized (lock) {
123 Iterator<IProperty> iterator = myPropertiesMap.get(key).iterator();
124 return iterator.hasNext() ? iterator.next() : null;
130 public List<IProperty> findPropertiesByKey(@NotNull String key) {
131 ensurePropertiesLoaded();
132 synchronized (lock) {
133 return ContainerUtil.collect(myPropertiesMap.get(key).iterator());
139 public ResourceBundle getResourceBundle() {
140 return PropertiesImplUtil.getResourceBundle(this);
145 public Locale getLocale() {
146 return PropertiesUtil.getLocale(this);
150 public PsiElement add(@NotNull PsiElement element) throws IncorrectOperationException {
151 if (element instanceof Property) {
152 throw new IncorrectOperationException("Use addProperty() instead");
154 return super.add(element);
159 public PsiElement addProperty(@NotNull IProperty property) throws IncorrectOperationException {
160 final IProperty position = findInsertionPosition(property);
161 return addPropertyAfter(property, position);
164 private IProperty findInsertionPosition(@NotNull IProperty property) {
165 synchronized (lock) {
166 ensurePropertiesLoaded();
167 if (myProperties.isEmpty()) {
171 final int insertIndex = Collections.binarySearch(myProperties, property, (p1, p2) -> {
172 final String k1 = p1.getKey();
173 final String k2 = p2.getKey();
174 LOG.assertTrue(k1 != null && k2 != null);
175 return String.CASE_INSENSITIVE_ORDER.compare(k1, k2);
177 return insertIndex == -1 ? null :myProperties.get(insertIndex < 0 ? - insertIndex - 2 : insertIndex);
179 return myProperties.get(myProperties.size() - 1);
185 public PsiElement addPropertyAfter(@NotNull final IProperty property, @Nullable final IProperty anchor) throws IncorrectOperationException {
186 final TreeElement copy = ChangeUtil.copyToElement(property.getPsiElement());
187 List<IProperty> properties = getProperties();
188 ASTNode anchorBefore = anchor == null ? properties.isEmpty() ? null : properties.get(0).getPsiElement().getNode()
189 : anchor.getPsiElement().getNode().getTreeNext();
190 if (anchorBefore != null) {
191 if (anchorBefore.getElementType() == TokenType.WHITE_SPACE) {
192 anchorBefore = anchorBefore.getTreeNext();
195 if (anchorBefore == null && haveToAddNewLine()) {
196 insertLineBreakBefore(null);
198 getPropertiesList().addChild(copy, anchorBefore);
199 if (anchorBefore != null) {
200 insertLineBreakBefore(anchorBefore);
202 return copy.getPsi();
207 public IProperty addProperty(String key, String value) {
208 return (IProperty)addProperty(PropertiesElementFactory.createProperty(getProject(), key, value, null));
213 public IProperty addPropertyAfter(String key, String value, @Nullable IProperty anchor) {
214 return (IProperty)addPropertyAfter(PropertiesElementFactory.createProperty(getProject(), key, value, null), anchor);
217 private void insertLineBreakBefore(final ASTNode anchorBefore) {
218 getPropertiesList().addChild(ASTFactory.whitespace("\n"), anchorBefore);
221 private boolean haveToAddNewLine() {
222 ASTNode lastChild = getPropertiesList().getLastChildNode();
223 return lastChild != null && !lastChild.getText().endsWith("\n");
228 public Map<String, String> getNamesMap() {
229 Map<String, String> result = new THashMap<String, String>();
230 for (IProperty property : getProperties()) {
231 result.put(property.getUnescapedKey(), property.getValue());
237 public boolean isAlphaSorted() {
238 synchronized (lock) {
239 ensurePropertiesLoaded();
240 return myAlphaSorted;
245 public void clearCaches() {
248 synchronized (lock) {
249 myPropertiesMap = null;