blob: d614743d630f322fb9f5e05c13cb97b3e6edbcf3 [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.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.lang.properties.PropertyManipulator;
import com.intellij.lang.properties.parsing.PropertiesTokenTypes;
import com.intellij.lang.properties.psi.PropertiesElementFactory;
import com.intellij.lang.properties.psi.PropertiesFile;
import com.intellij.lang.properties.psi.Property;
import com.intellij.lang.properties.psi.PropertyStub;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
/**
* @author max
*/
public class PropertyImpl extends PropertiesStubElementImpl<PropertyStub> implements Property, PsiLanguageInjectionHost {
private static final Logger LOG = Logger.getInstance("#com.intellij.lang.properties.psi.impl.PropertyImpl");
public PropertyImpl(@NotNull ASTNode node) {
super(node);
}
public PropertyImpl(final PropertyStub stub, final IStubElementType nodeType) {
super(stub, nodeType);
}
public String toString() {
return "Property:" + getKey();
}
public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
PropertyImpl property = (PropertyImpl)PropertiesElementFactory.createProperty(getProject(), name, "xxx");
ASTNode keyNode = getKeyNode();
ASTNode newKeyNode = property.getKeyNode();
LOG.assertTrue(newKeyNode != null);
if (keyNode == null) {
getNode().addChild(newKeyNode);
}
else {
getNode().replaceChild(keyNode, newKeyNode);
}
return this;
}
public void setValue(@NotNull String value) throws IncorrectOperationException {
ASTNode node = getValueNode();
PropertyImpl property = (PropertyImpl)PropertiesElementFactory.createProperty(getProject(), "xxx", value);
ASTNode valueNode = property.getValueNode();
if (node == null) {
if (valueNode != null) {
getNode().addChild(valueNode);
}
}
else {
if (valueNode == null) {
getNode().removeChild(node);
}
else {
getNode().replaceChild(node, valueNode);
}
}
}
public String getName() {
return getUnescapedKey();
}
public String getKey() {
final PropertyStub stub = getStub();
if (stub != null) {
return stub.getKey();
}
final ASTNode node = getKeyNode();
if (node == null) {
return null;
}
return node.getText();
}
@Nullable
public ASTNode getKeyNode() {
return getNode().findChildByType(PropertiesTokenTypes.KEY_CHARACTERS);
}
@Nullable
public ASTNode getValueNode() {
return getNode().findChildByType(PropertiesTokenTypes.VALUE_CHARACTERS);
}
public String getValue() {
final ASTNode node = getValueNode();
if (node == null) {
return "";
}
return node.getText();
}
@Nullable
public String getUnescapedValue() {
return unescape(getValue());
}
public static String unescape(String s) {
if (s == null) return null;
StringBuilder sb = new StringBuilder();
parseCharacters(s, sb, null);
return sb.toString();
}
public static boolean parseCharacters(String s, StringBuilder outChars, @Nullable int[] sourceOffsets) {
assert sourceOffsets == null || sourceOffsets.length == s.length() + 1;
int off = 0;
int len = s.length();
boolean result = true;
final int outOffset = outChars.length();
while (off < len) {
char aChar = s.charAt(off++);
if (sourceOffsets != null) {
sourceOffsets[outChars.length() - outOffset] = off - 1;
sourceOffsets[outChars.length() + 1 - outOffset] = off;
}
if (aChar == '\\') {
aChar = s.charAt(off++);
if (aChar == 'u') {
// Read the xxxx
int value = 0;
boolean error = false;
for (int i = 0; i < 4 && off < s.length(); i++) {
aChar = s.charAt(off++);
switch (aChar) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
value = (value << 4) + aChar - '0';
break;
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
value = (value << 4) + 10 + aChar - 'a';
break;
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
value = (value << 4) + 10 + aChar - 'A';
break;
default:
outChars.append("\\u");
int start = off - i - 1;
int end = start + 4 < s.length() ? start + 4 : s.length();
outChars.append(s, start, end);
i=4;
error = true;
off = end;
break;
//throw new IllegalArgumentException("Malformed \\uxxxx encoding.");
}
}
if (!error) {
outChars.append((char)value);
}
else {
result = false;
}
}
else if (aChar == '\n') {
// escaped linebreak: skip whitespace in the beginning of next line
while (off < len && (s.charAt(off) == ' ' || s.charAt(off) == '\t')) {
off++;
}
}
else if (aChar == 't') {
outChars.append('\t');
}
else if (aChar == 'r') {
outChars.append('\r');
}
else if (aChar == 'n') {
outChars.append('\n');
}
else if (aChar == 'f') {
outChars.append('\f');
}
else {
outChars.append(aChar);
}
}
else {
outChars.append(aChar);
}
if (sourceOffsets != null) {
sourceOffsets[outChars.length() - outOffset] = off;
}
}
return result;
}
public static TextRange trailingSpaces(String s) {
if (s == null) {
return null;
}
int off = 0;
int len = s.length();
int startSpaces = -1;
while (off < len) {
char aChar = s.charAt(off++);
if (aChar == '\\') {
if (startSpaces == -1) startSpaces = off-1;
aChar = s.charAt(off++);
if (aChar == 'u') {
// Read the xxxx
int value = 0;
boolean error = false;
for (int i = 0; i < 4; i++) {
aChar = off < s.length() ? s.charAt(off++) : 0;
switch (aChar) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
value = (value << 4) + aChar - '0';
break;
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
value = (value << 4) + 10 + aChar - 'a';
break;
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
value = (value << 4) + 10 + aChar - 'A';
break;
default:
int start = off - i - 1;
int end = start + 4 < s.length() ? start + 4 : s.length();
i=4;
error = true;
off = end;
startSpaces = -1;
break;
}
}
if (!error) {
if (Character.isWhitespace(value)) {
if (startSpaces == -1) {
startSpaces = off-1;
}
}
else {
startSpaces = -1;
}
}
}
else if (aChar == '\n') {
// escaped linebreak: skip whitespace in the beginning of next line
while (off < len && (s.charAt(off) == ' ' || s.charAt(off) == '\t')) {
off++;
}
}
else if (aChar == 't') {
if (startSpaces == -1) startSpaces = off;
}
else if (aChar == 'r') {
if (startSpaces == -1) startSpaces = off;
}
else if (aChar == 'n') {
if (startSpaces == -1) startSpaces = off;
}
else if (aChar == 'f') {
if (startSpaces == -1) startSpaces = off;
}
else {
if (Character.isWhitespace(aChar)) {
if (startSpaces == -1) {
startSpaces = off-1;
}
}
else {
startSpaces = -1;
}
}
}
else {
if (Character.isWhitespace(aChar)) {
if (startSpaces == -1) {
startSpaces = off-1;
}
}
else {
startSpaces = -1;
}
}
}
return startSpaces == -1 ? null : new TextRange(startSpaces, len);
}
@Nullable
public String getUnescapedKey() {
return unescape(getKey());
}
public Icon getIcon(int flags) {
return PlatformIcons.PROPERTY_ICON;
}
public void delete() throws IncorrectOperationException {
final ASTNode parentNode = getParent().getNode();
assert parentNode != null;
ASTNode node = getNode();
ASTNode prev = node.getTreePrev();
ASTNode next = node.getTreeNext();
parentNode.removeChild(node);
if ((prev == null || prev.getElementType() == TokenType.WHITE_SPACE) && next != null &&
next.getElementType() == TokenType.WHITE_SPACE) {
parentNode.removeChild(next);
}
}
public PropertiesFile getPropertiesFile() {
return (PropertiesFile)super.getContainingFile();
}
public String getDocCommentText() {
StringBuilder text = new StringBuilder();
for (PsiElement doc = getPrevSibling(); doc != null; doc = doc.getPrevSibling()) {
if (doc instanceof PsiWhiteSpace) {
doc = doc.getPrevSibling();
}
if (doc instanceof PsiComment) {
if (text.length() != 0) text.insert(0, "\n");
String comment = doc.getText();
String trimmed = StringUtil.trimStart(StringUtil.trimStart(comment, "#"), "!");
text.insert(0, trimmed.trim());
}
else {
break;
}
}
if (text.length() == 0) return null;
return text.toString();
}
@NotNull
@Override
public PsiElement getPsiElement() {
return this;
}
@NotNull
public SearchScope getUseScope() {
// property ref can occur in any file
return GlobalSearchScope.allScope(getProject());
}
public ItemPresentation getPresentation() {
return new ItemPresentation() {
public String getPresentableText() {
return getName();
}
public String getLocationString() {
return getPropertiesFile().getName();
}
public Icon getIcon(final boolean open) {
return null;
}
};
}
@Override
public boolean isValidHost() {
return true;
}
@Override
public PsiLanguageInjectionHost updateText(@NotNull String text) {
return new PropertyManipulator().handleContentChange(this, text);
}
@NotNull
@Override
public LiteralTextEscaper<? extends PsiLanguageInjectionHost> createLiteralTextEscaper() {
return new PropertyImplEscaper(this);
}
}