speed up macro collapse — 200ms down to 90ms

This commit is contained in:
Vladimir Krivosheev
2018-07-27 17:54:04 +02:00
parent b0481e8b3d
commit 4e4d380722
6 changed files with 100 additions and 104 deletions
@@ -1,9 +1,8 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.application;
import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.Text;
import org.jetbrains.annotations.NotNull;
/**
@@ -16,18 +15,10 @@ public abstract class PathMacroFilter {
return false;
}
public boolean skipPathMacros(Text element) {
return false;
}
public boolean skipPathMacros(@NotNull Attribute attribute) {
return false;
}
public boolean recursePathMacros(Text element) {
return false;
}
public boolean recursePathMacros(Attribute attribute) {
return false;
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2016 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.components;
import com.intellij.openapi.application.PathMacroFilter;
@@ -40,26 +26,22 @@ public abstract class PathMacroMap {
substitute(e, caseSensitive, false);
}
public final void substitute(@NotNull Element e, boolean caseSensitive, boolean recursively, @Nullable PathMacroFilter filter) {
if (filter != null && filter.skipPathMacros(e)) {
public final void substitute(@NotNull Element element, boolean caseSensitive, boolean recursively, @Nullable PathMacroFilter filter) {
if (filter != null && filter.skipPathMacros(element)) {
return;
}
for (Content child : e.getContent()) {
for (Content child : element.getContent()) {
if (child instanceof Element) {
substitute((Element)child, caseSensitive, recursively, filter);
}
else if (child instanceof Text) {
Text t = (Text)child;
if (filter == null || !filter.skipPathMacros(t)) {
String oldText = t.getText();
String newText = (recursively || (filter != null && filter.recursePathMacros(t)))
? substituteRecursively(oldText, caseSensitive)
: substitute(oldText, caseSensitive);
if (oldText != newText) {
// it is faster to call 'setText' right away than perform additional 'equals' check
t.setText(newText);
}
String oldText = t.getText();
String newText = recursively ? substituteRecursively(oldText, caseSensitive) : substitute(oldText, caseSensitive);
if (oldText != newText) {
// it is faster to call 'setText' right away than perform additional 'equals' check
t.setText(newText);
}
}
else if (!(child instanceof Comment)) {
@@ -67,13 +49,14 @@ public abstract class PathMacroMap {
}
}
for (Attribute attribute : e.getAttributes()) {
if (!element.hasAttributes()) {
return;
}
for (Attribute attribute : element.getAttributes()) {
if (filter == null || !filter.skipPathMacros(attribute)) {
String oldValue = attribute.getValue();
String newValue = (recursively || (filter != null && filter.recursePathMacros(attribute)))
? substituteRecursively(oldValue, caseSensitive)
: substitute(oldValue, caseSensitive);
if (oldValue != newValue) {
String newValue = getAttributeValue(attribute, filter, caseSensitive, recursively);
if (attribute.getValue() != newValue) {
// it is faster to call 'setValue' right away than perform additional 'equals' check
attribute.setValue(newValue);
}
@@ -81,6 +64,16 @@ public abstract class PathMacroMap {
}
}
public String getAttributeValue(@NotNull Attribute attribute, @Nullable PathMacroFilter filter, boolean caseSensitive, boolean recursively) {
String oldValue = attribute.getValue();
if (recursively || (filter != null && filter.recursePathMacros(attribute))) {
return substituteRecursively(oldValue, caseSensitive);
}
else {
return substitute(oldValue, caseSensitive);
}
}
public final void substitute(@NotNull Element e, boolean caseSensitive, final boolean recursively) {
substitute(e, caseSensitive, recursively, null);
}
@@ -52,7 +52,10 @@
*/
package com.intellij.configurationStore;
import com.intellij.application.options.ReplacePathToMacroMap;
import com.intellij.openapi.application.PathMacroFilter;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.SystemInfoRt;
import com.intellij.openapi.util.text.StringUtil;
import org.jdom.*;
import org.jdom.output.Format;
@@ -65,7 +68,7 @@ import java.io.Writer;
import java.util.List;
@SuppressWarnings("Duplicates")
class JbXmlOutputter {
final class JbXmlOutputter {
private static final Format DEFAULT_FORMAT = JDOMUtil.createFormat("\n");
// For normal output
@@ -73,9 +76,19 @@ class JbXmlOutputter {
@Nullable
private final JDOMUtil.ElementOutputFilter elementFilter;
public JbXmlOutputter(@NotNull String lineSeparator, @Nullable JDOMUtil.ElementOutputFilter elementFilter) {
@Nullable
private final ReplacePathToMacroMap macroMap;
@Nullable
private final PathMacroFilter macroFilter;
public JbXmlOutputter(@NotNull String lineSeparator,
@Nullable JDOMUtil.ElementOutputFilter elementFilter,
@Nullable ReplacePathToMacroMap macroMap,
@Nullable PathMacroFilter macroFilter) {
this.format = DEFAULT_FORMAT.getLineSeparator().equals(lineSeparator) ? DEFAULT_FORMAT : JDOMUtil.createFormat(lineSeparator);
this.elementFilter = elementFilter;
this.macroMap = macroMap;
this.macroFilter = macroFilter;
}
/**
@@ -169,7 +182,7 @@ class JbXmlOutputter {
* @param out <code>Writer</code> to use.
* @param encoding The encoding to add to the declaration
*/
protected void printDeclaration(Writer out, String encoding) throws IOException {
private void printDeclaration(Writer out, String encoding) throws IOException {
// Only print the declaration if it's not being omitted
if (!format.getOmitDeclaration()) {
// Assume 1.0 version
@@ -192,7 +205,7 @@ class JbXmlOutputter {
* @param docType <code>Document</code> whose declaration to write.
* @param out <code>Writer</code> to use.
*/
protected void printDocType(Writer out, DocType docType) throws IOException {
private void printDocType(Writer out, DocType docType) throws IOException {
String publicID = docType.getPublicID();
String systemID = docType.getSystemID();
@@ -230,7 +243,7 @@ class JbXmlOutputter {
* @param comment <code>Comment</code> to write.
* @param out <code>Writer</code> to use.
*/
protected void printComment(Writer out, Comment comment)
private static void printComment(Writer out, Comment comment)
throws IOException {
out.write("<!--");
out.write(comment.getText());
@@ -243,7 +256,7 @@ class JbXmlOutputter {
* @param pi <code>ProcessingInstruction</code> to write.
* @param out <code>Writer</code> to use.
*/
protected void printProcessingInstruction(Writer out, ProcessingInstruction pi) throws IOException {
private void printProcessingInstruction(Writer out, ProcessingInstruction pi) throws IOException {
String target = pi.getTarget();
boolean piProcessed = false;
@@ -283,7 +296,7 @@ class JbXmlOutputter {
* @param entity <code>EntityRef</code> to output.
* @param out <code>Writer</code> to use.
*/
protected void printEntityRef(Writer out, EntityRef entity)
private static void printEntityRef(Writer out, EntityRef entity)
throws IOException {
out.write("&");
out.write(entity.getName());
@@ -296,7 +309,7 @@ class JbXmlOutputter {
* @param cdata <code>CDATA</code> to output.
* @param out <code>Writer</code> to use.
*/
protected void printCDATA(Writer out, CDATA cdata) throws IOException {
private void printCDATA(Writer out, CDATA cdata) throws IOException {
String str;
if (format.getTextMode() == Format.TextMode.NORMALIZE) {
str = cdata.getTextNormalize();
@@ -320,6 +333,11 @@ class JbXmlOutputter {
else if (format.getTextMode() == Format.TextMode.TRIM) {
str = str.trim();
}
if (macroMap != null) {
str = macroMap.substitute(str, SystemInfoRt.isFileSystemCaseSensitive);
}
out.write(escapeElementEntities(str));
}
@@ -328,11 +346,11 @@ class JbXmlOutputter {
* its <code>{@link Attribute}</code>s, and all contained (child)
* elements, etc.
*
* @param element <code>Element</code> to output.
* @param out <code>Writer</code> to use.
* @param level <code>int</code> level of indention.
* @param element <code>Element</code> to output.
* @param out <code>Writer</code> to use.
* @param level <code>int</code> level of indention.
*/
protected void printElement(Writer out, Element element, int level) throws IOException {
public void printElement(Writer out, Element element, int level) throws IOException {
if (elementFilter != null && !elementFilter.accept(element, level)) {
return;
}
@@ -366,17 +384,16 @@ class JbXmlOutputter {
else {
out.write('>');
// For a special case where the content is only CDATA
// or Text we don't want to indent after the start or before the end tag.
// for a special case where the content is only CDATA or Text we don't want to indent after the start or before the end tag
if (nextNonText(content, start) < size) {
// Case Mixed Content - normal indentation
// case Mixed Content - normal indentation
newline(out);
printContentRange(out, content, start, size, level + 1);
newline(out);
indent(out, level);
}
else {
// Case all CDATA or Text - no indentation
// case all CDATA or Text - no indentation
printTextRange(out, content, start, size);
}
out.write("</");
@@ -391,11 +408,11 @@ class JbXmlOutputter {
* starting index is inclusive, while the ending index is
* exclusive.
*
* @param content <code>List</code> of content to output
* @param start index of first content node (inclusive.
* @param end index of last content node (exclusive).
* @param out <code>Writer</code> to use.
* @param level <code>int</code> level of indentation.
* @param content <code>List</code> of content to output
* @param start index of first content node (inclusive.
* @param end index of last content node (exclusive).
* @param out <code>Writer</code> to use.
* @param level <code>int</code> level of indentation.
*/
private void printContentRange(Writer out, List<Content> content, int start, int end, int level) throws IOException {
boolean firstNode; // Flag for 1st node in content
@@ -441,13 +458,11 @@ class JbXmlOutputter {
printProcessingInstruction(out, (ProcessingInstruction)next);
}
else {
// XXX if we get here then we have a illegal content, for
// now we'll just ignore it (probably should throw
// a exception)
// XXX if we get here then we have a illegal content, for now we'll just ignore it (probably should throw a exception)
}
index++;
} /* while */
}
}
/**
@@ -467,18 +482,18 @@ class JbXmlOutputter {
previous = null;
// Remove leading whitespace-only nodes
// remove leading whitespace-only nodes
start = skipLeadingWhite(content, start);
int size = content.size();
if (start < size) {
// And remove trialing whitespace-only nodes
// and remove trialing whitespace-only nodes
end = skipTrailingWhite(content, end);
for (int i = start; i < end; i++) {
node = content.get(i);
// Get the unmangled version of the text we are about to print
// get the unmangled version of the text we are about to print
if (node instanceof Text) {
next = ((Text)node).getText();
}
@@ -489,13 +504,12 @@ class JbXmlOutputter {
throw new IllegalStateException("Should see only CDATA, Text, or EntityRef");
}
// This may save a little time
// this may save a little time
if (next == null || next.isEmpty()) {
continue;
}
// Determine if we need to pad the output (padding is
// only need in trim or normalizing mode)
// determine if we need to pad the output (padding is only need in trim or normalizing mode)
if (previous != null) { // Not 1st node
if (format.getTextMode() == Format.TextMode.NORMALIZE || format.getTextMode() == Format.TextMode.TRIM) {
if (endsWithWhite(previous) || startsWithWhite(next)) {
@@ -526,13 +540,22 @@ class JbXmlOutputter {
* @param attributes <code>List</code> of Attribute objects
* @param out <code>Writer</code> to use
*/
protected void printAttributes(Writer out, List<Attribute> attributes) throws IOException {
private void printAttributes(Writer out, List<Attribute> attributes) throws IOException {
for (Attribute attribute : attributes) {
out.write(' ');
printQualifiedName(out, attribute);
out.write('=');
out.write('"');
out.write(escapeAttributeEntities(attribute.getValue()));
String value;
if (macroMap != null && (macroFilter == null || !macroFilter.skipPathMacros(attribute))) {
value = macroMap.getAttributeValue(attribute, macroFilter, SystemInfoRt.isFileSystemCaseSensitive, false);
}
else {
value = attribute.getValue();
}
out.write(escapeAttributeEntities(value));
out.write('"');
}
}
@@ -9,7 +9,7 @@ import com.intellij.openapi.components.impl.stores.FileStorageCoreUtil
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.runAndLogException
import com.intellij.openapi.util.JDOMUtil
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream
import com.intellij.openapi.vfs.safeOutputStream
import com.intellij.util.LineSeparator
@@ -230,7 +230,7 @@ private class XmlDataWriter(private val rootElementName: String?,
writer.append(entry.key)
writer.append('=')
writer.append('"')
writer.append(replacePathMap?.substitute(JDOMUtil.escapeText(entry.value, false, true), SystemInfo.isFileSystemCaseSensitive))
writer.append(replacePathMap?.substitute(JDOMUtil.escapeText(entry.value, false, true), SystemInfoRt.isFileSystemCaseSensitive))
writer.append('"')
}
@@ -243,13 +243,12 @@ private class XmlDataWriter(private val rootElementName: String?,
writer.append('>')
}
val xmlOutputter = JbXmlOutputter(lineSeparatorWithIndent, filter?.toElementFilter())
val xmlOutputter = JbXmlOutputter(lineSeparatorWithIndent, filter?.toElementFilter(), replacePathMap, macroFilter)
for (element in elements) {
if (hasRootElement) {
writer.append(lineSeparatorWithIndent)
}
replacePathMap?.substitute(element, SystemInfo.isFileSystemCaseSensitive, false, macroFilter)
xmlOutputter.printElement(writer, element, 0)
}
@@ -414,7 +413,7 @@ internal fun createDataWriterForElement(element: Element): DataWriter {
override fun hasData(filter: DataWriterFilter) = filter.hasData(element)
override fun write(output: OutputStream, lineSeparator: String, filter: DataWriterFilter?) {
output.bufferedWriter().use { JbXmlOutputter(lineSeparator, filter?.toElementFilter()).output(element, it) }
output.bufferedWriter().use { JbXmlOutputter(lineSeparator, filter?.toElementFilter(), null, null).output(element, it) }
}
}
}
@@ -5,6 +5,7 @@ import com.intellij.openapi.components.PathMacroMap;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.util.text.StringUtilRt;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
@@ -79,9 +80,9 @@ public class ReplacePathToMacroMap extends PathMacroMap {
return text;
}
boolean startsWith = caseSensitive ? text.startsWith(path) : StringUtil.startsWithIgnoreCase(text, path);
if (!startsWith) return text;
if (!(caseSensitive ? text.startsWith(path) : StringUtilRt.startsWithIgnoreCase(text, path))) {
return text;
}
//check that this is complete path (ends with "/" or "!/")
// do not collapse partial paths, i.e. do not substitute "/a/b/cd" in paths like "/a/b/cdeFgh"
@@ -90,11 +91,17 @@ public class ReplacePathToMacroMap extends PathMacroMap {
if (!isWindowsRoot &&
endOfOccurrence < text.length() &&
text.charAt(endOfOccurrence) != '/' &&
!text.substring(endOfOccurrence).startsWith("!/")) {
!(text.charAt(endOfOccurrence) == '!' && text.substring(endOfOccurrence).startsWith("!/"))) {
return text;
}
return myMacroMap.get(path) + text.substring(endOfOccurrence);
String s = myMacroMap.get(path);
if (text.length() > endOfOccurrence) {
return s + text.substring(endOfOccurrence);
}
else {
return s;
}
}
@NotNull
@@ -1,10 +1,9 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.components;
import com.intellij.openapi.application.PathMacroFilter;
import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.Text;
import org.jetbrains.annotations.NotNull;
/**
@@ -27,14 +26,6 @@ public class CompositePathMacroFilter extends PathMacroFilter {
return false;
}
@Override
public boolean skipPathMacros(Text element) {
for (PathMacroFilter filter : myFilters) {
if (filter.skipPathMacros(element)) return true;
}
return false;
}
@Override
public boolean skipPathMacros(@NotNull Attribute attribute) {
for (PathMacroFilter filter : myFilters) {
@@ -45,14 +36,6 @@ public class CompositePathMacroFilter extends PathMacroFilter {
return false;
}
@Override
public boolean recursePathMacros(Text element) {
for (PathMacroFilter filter : myFilters) {
if (filter.recursePathMacros(element)) return true;
}
return false;
}
@Override
public boolean recursePathMacros(Attribute attribute) {
for (PathMacroFilter filter : myFilters) {