/*
* Copyright 2000-2007 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.openapi.util;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.StringInterner;
import com.intellij.util.io.URLUtil;
import com.intellij.util.io.fs.IFile;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.util.text.CharSequenceReader;
import org.jdom.*;
import org.jdom.filter.Filter;
import org.jdom.input.DOMBuilder;
import org.jdom.input.SAXBuilder;
import org.jdom.output.DOMOutputter;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author mike
*/
@SuppressWarnings({"HardCodedStringLiteral"})
public class JDOMUtil {
//Logger is lasy-initialized in order not to use it outside the appClassLoader
private static Logger ourLogger = null;
private static ThreadLocal<SAXBuilder> ourSaxBuilder = new ThreadLocal<SAXBuilder>(){
protected SAXBuilder initialValue() {
SAXBuilder saxBuilder = new SAXBuilder();
saxBuilder.setEntityResolver(new EntityResolver() {
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
return new InputSource(new CharArrayReader(ArrayUtil.EMPTY_CHAR_ARRAY));
}
});
return saxBuilder;
}
};
private static Logger getLogger() {
if (ourLogger == null) ourLogger = Logger.getInstance("#com.intellij.openapi.util.JDOMUtil");
return ourLogger;
}
public static final String ENCODING = "UTF-8";
public static boolean areElementsEqual(Element e1, Element e2) {
if (e1 == null && e2 == null) return true;
if (e1 == null) return false;
return attListsEqual(e1.getAttributes(), e2.getAttributes()) &&
contentListsEqual(e1.getContent(CONTENT_FILTER), e2.getContent(CONTENT_FILTER));
}
private static final EmptyTextFilter CONTENT_FILTER = new EmptyTextFilter();
public static int getTreeHash(final Element root) {
return addToHash(0, root);
}
public static int getTreeHash(Document document) {
return getTreeHash(document.getRootElement());
}
private static int addToHash(int i, final Element element) {
i = addToHash(i, element.getName());
i = addToHash(i, element.getText());
final List list = element.getAttributes();
for (int j = 0; j < list.size(); j++) {
Attribute attribute = (Attribute)list.get(j);
i = addToHash(i, attribute);
}
//noinspection unchecked
final List<Element> children = element.getChildren();
for (Element child : children) { //iterator is used here which is more efficient than get(index)
i = addToHash(i, child);
}
return i;
}
private static int addToHash(int i, final Attribute attribute) {
i = addToHash(i, attribute.getName());
i = addToHash(i, attribute.getValue());
return i;
}
private static int addToHash(final int i, final String s) {
return i * 31 + s.hashCode();
}
@SuppressWarnings({"unchecked"})
public static Object[] getChildNodesWithAttrs(final Element e) {
ArrayList<Object> result = new ArrayList<Object>();
result.addAll(e.getContent());
result.addAll(e.getAttributes());
return result.toArray(new Object[result.size()]);
}
@SuppressWarnings({"unchecked"})
public static Content[] getContent(final Element m) {
final List list = m.getContent();
return (Content[])list.toArray(new Content[list.size()]);
}
@SuppressWarnings({"unchecked"})
public static Element[] getElements(final Element m) {
final List list = m.getChildren();
return (Element[])list.toArray(new Element[list.size()]);
}
public static String concatTextNodesValues(final Object[] nodes) {
StringBuffer result = new StringBuffer();
for (Object node : nodes) {
result.append(((Content)node).getValue());
}
return result.toString();
}
public static void addContent(final Element targetElement, final Object node) {
if (node instanceof Content) {
Content content = (Content)node;
targetElement.addContent(content);
}
else if (node instanceof List) {
List list = (List)node;
targetElement.addContent(list);
}
else {
throw new IllegalArgumentException("Wrong node: " + node);
}
}
public static void internElement(final Element element, final StringInterner interner) {
element.setName(intern(interner, element.getName()));
final List attributes = element.getAttributes();
for (Object o : attributes) {
Attribute attr = (Attribute)o;
attr.setName(intern(interner, attr.getName()));
attr.setValue(intern(interner, attr.getValue()));
}
final List content = element.getContent();
for (Object o : content) {
if (o instanceof Element) {
Element e = (Element)o;
internElement(e, interner);
}
else if (o instanceof Text) {
Text text = (Text)o;
text.setText(intern(interner, text.getText()));
}
else if (o instanceof Comment) {
Comment comment = (Comment)o;
comment.setText(intern(interner, comment.getText()));
}
else {
throw new IllegalArgumentException("Wrong node: " + o);
}
}
}
private static String intern(final StringInterner interner, final String s) {
synchronized (interner) {
return interner.intern(s);
}
}
private static class EmptyTextFilter implements Filter {
public boolean matches(Object obj) {
if (obj instanceof Text) {
final Text t = (Text)obj;
return !CharArrayUtil.containsOnlyWhiteSpaces(t.getText());
}
return true;
}
}
private static boolean contentListsEqual(final List c1, final List c2) {
if (c1 == null && c2 == null) return true;
if (c1 == null) return false;
Iterator l1 = c1.listIterator();
Iterator l2 = c2.listIterator();
while (l1.hasNext() && l2.hasNext()) {
if (!contentsEqual((Content)l1.next(), (Content)l2.next())) {
return false;
}
}
return l1.hasNext() == l2.hasNext();
}
private static boolean contentsEqual(Content c1, Content c2) {
if (!(c1 instanceof Element) && !(c2 instanceof Element)) {
return c1.getValue().equals(c2.getValue());
}
return c1 instanceof Element && c2 instanceof Element && areElementsEqual((Element)c1, (Element)c2);
}
private static boolean attListsEqual(List a1, List a2) {
if (a1.size() != a2.size()) return false;
for (int i = 0; i < a1.size(); i++) {
if (!attEqual((Attribute)a1.get(i), (Attribute)a2.get(i))) return false;
}
return true;
}
private static boolean attEqual(Attribute a1, Attribute a2) {
return a1.getName().equals(a2.getName()) && a1.getValue().equals(a2.getValue());
}
public static boolean areDocumentsEqual(Document d1, Document d2) {
if(d1.hasRootElement() != d2.hasRootElement()) return false;
if(!d1.hasRootElement()) return true;
CharArrayWriter w1 = new CharArrayWriter();
CharArrayWriter w2 = new CharArrayWriter();
try {
writeDocument(d1, w1, "\n");
writeDocument(d2, w2, "\n");
}
catch (IOException e) {
getLogger().error(e);
}
return w1.size() == w2.size() && w1.toString().equals(w2.toString());
}
public static Document loadDocument(char[] chars, int length) throws IOException, JDOMException {
SAXBuilder builder = ourSaxBuilder.get();
return builder.build(new CharArrayReader(chars, 0, length));
}
public static Document loadDocument(CharSequence seq) throws IOException, JDOMException {
SAXBuilder builder = ourSaxBuilder.get();
return builder.build(new CharSequenceReader(seq));
}
public static Document loadDocument(File file) throws JDOMException, IOException {
return loadDocument(new BufferedInputStream(new FileInputStream(file)));
}
public static Document loadDocument(final IFile iFile) throws IOException, JDOMException {
return loadDocument(new BufferedInputStream(iFile.openInputStream()));
}
public static Document loadDocument(@NotNull InputStream stream) throws JDOMException, IOException {
SAXBuilder saxBuilder = ourSaxBuilder.get();
InputStreamReader reader = new InputStreamReader(stream, ENCODING);
try {
return saxBuilder.build(reader);
}
finally {
reader.close();
}
}
public static Document loadDocument(URL url) throws JDOMException, IOException {
return loadDocument(URLUtil.openStream(url));
}
public static void writeDocument(Document document, String filePath, String lineSeparator) throws IOException {
OutputStream stream = new BufferedOutputStream(new FileOutputStream(filePath));
try {
writeDocument(document, stream, lineSeparator);
}
finally {
stream.close();
}
}
public static void writeDocument(Document document, File file, String lineSeparator) throws IOException {
OutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
try {
writeDocument(document, stream, lineSeparator);
}
finally {
stream.close();
}
}
public static void writeDocument(Document document, OutputStream stream, String lineSeparator) throws IOException {
writeDocument(document, new OutputStreamWriter(stream, ENCODING), lineSeparator);
}
public static byte[] printDocument(Document document, String lineSeparator) throws IOException {
CharArrayWriter writer = new CharArrayWriter();
writeDocument(document, writer, lineSeparator);
return new String(writer.toCharArray()).getBytes(ENCODING);
}
public static String writeDocument(Document document, String lineSeparator) {
try {
final StringWriter writer = new StringWriter();
writeDocument(document, writer, lineSeparator);
return writer.toString();
}
catch (IOException e) {
// Can't be
return "";
}
}
public static void writeElement(Element element, Writer writer, String lineSeparator) throws IOException {
XMLOutputter xmlOutputter = createOutputter(lineSeparator);
try {
xmlOutputter.output(element, writer);
}
catch (NullPointerException ex) {
getLogger().error(ex);
printDiagnostics(element, "");
}
}
public static String writeElement(Element element, String lineSeparator) throws IOException {
final StringWriter writer = new StringWriter();
writeElement(element, writer, lineSeparator);
return writer.toString();
}
public static void writeDocument(Document document, Writer writer, String lineSeparator) throws IOException {
XMLOutputter xmlOutputter = createOutputter(lineSeparator);
try {
xmlOutputter.output(document, writer);
writer.close();
}
catch (NullPointerException ex) {
getLogger().error(ex);
printDiagnostics(document.getRootElement(), "");
}
}
public static XMLOutputter createOutputter(String lineSeparator) {
XMLOutputter xmlOutputter = new MyXMLOutputter();
Format format = Format.getCompactFormat().
setIndent(" ").
setTextMode(Format.TextMode.NORMALIZE).
setEncoding(ENCODING).
setOmitEncoding(false).
setOmitDeclaration(false).
setLineSeparator(lineSeparator);
xmlOutputter.setFormat(format);
return xmlOutputter;
}
/**
* Returns null if no escapement necessary.
*/
@Nullable
private static String escapeChar(char c, boolean escapeLineEnds) {
switch (c) {
case '\n': return escapeLineEnds ? " " : null;
case '\r': return escapeLineEnds ? " " : null;
case '\t': return escapeLineEnds ? "	" : null;
case '<': return "<";
case '>': return ">";
case '\"': return """;
case '&': return "&";
}
return null;
}
public static String escapeText(String text) {
return escapeText(text, false);
}
private static String escapeText(String text, boolean escapeLineEnds) {
StringBuffer buffer = null;
for (int i = 0; i < text.length(); i++) {
final char ch = text.charAt(i);
final String quotation = escapeChar(ch, escapeLineEnds);
if (buffer == null) {
if (quotation != null) {
// An quotation occurred, so we'll have to use StringBuffer
// (allocate room for it plus a few more entities).
buffer = new StringBuffer(text.length() + 20);
// Copy previous skipped characters and fall through
// to pickup current character
buffer.append(text.substring(0, i));
buffer.append(quotation);
}
}
else {
if (quotation == null) {
buffer.append(ch);
}
else {
buffer.append(quotation);
}
}
}
// If there were any entities, return the escaped characters
// that we put in the StringBuffer. Otherwise, just return
// the unmodified input string.
return (buffer == null) ? text : buffer.toString();
}
public static List<Element> getChildrenFromAllNamespaces(final Element element, @NonNls final String name) {
final ArrayList<Element> result = new ArrayList<Element>();
final List children = element.getChildren();
for (final Object aChildren : children) {
Element child = (Element)aChildren;
if (name.equals(child.getName())) {
result.add(child);
}
}
return result;
}
public static class MyXMLOutputter extends XMLOutputter {
public String escapeAttributeEntities(String str) {
return escapeText(str, true);
}
public String escapeElementEntities(String str) {
return escapeText(str, false);
}
}
private static void printDiagnostics(Element element, String prefix) {
ElementInfo info = getElementInfo(element);
prefix = prefix + "/" + info.name;
if (info.hasNullAttributes) {
System.err.println(prefix);
}
//noinspection unchecked
List<Element> children = element.getChildren();
for (final Element child : children) {
printDiagnostics(child, prefix);
}
}
private static ElementInfo getElementInfo(Element element) {
ElementInfo info = new ElementInfo();
StringBuffer buf = new StringBuffer(element.getName());
List attributes = element.getAttributes();
if (attributes != null) {
int length = attributes.size();
if (length > 0) {
buf.append("[");
for (int idx = 0; idx < length; idx++) {
Attribute attr = (Attribute)attributes.get(idx);
if (idx != 0) {
buf.append(";");
}
buf.append(attr.getName());
buf.append("=");
buf.append(attr.getValue());
if (attr.getValue() == null) {
info.hasNullAttributes = true;
}
}
buf.append("]");
}
}
info.name = buf.toString();
return info;
}
public static void updateFileSet(File[] oldFiles, String[] newFilePaths, Document[] newFileDocuments, String lineSeparator)
throws IOException {
getLogger().assertTrue(newFilePaths.length == newFileDocuments.length);
ArrayList<String> writtenFilesPaths = new ArrayList<String>();
// check if files are writable
for (String newFilePath : newFilePaths) {
File file = new File(newFilePath);
if (file.exists() && !file.canWrite()) {
throw new IOException("File \"" + newFilePath + "\" is not writeable");
}
}
for (File file : oldFiles) {
if (file.exists() && !file.canWrite()) {
throw new IOException("File \"" + file.getAbsolutePath() + "\" is not writeable");
}
}
for (int i = 0; i < newFilePaths.length; i++) {
String newFilePath = newFilePaths[i];
JDOMUtil.writeDocument(newFileDocuments[i], newFilePath, lineSeparator);
writtenFilesPaths.add(newFilePath);
}
// delete files if necessary
outer:
for (File oldFile : oldFiles) {
String oldFilePath = oldFile.getAbsolutePath();
for (final String writtenFilesPath : writtenFilesPaths) {
if (oldFilePath.equals(writtenFilesPath)) {
continue outer;
}
}
boolean result = oldFile.delete();
if (!result) {
throw new IOException("File \"" + oldFilePath + "\" was not deleted");
}
}
}
private static class ElementInfo {
public String name = "";
public boolean hasNullAttributes = false;
}
public static org.w3c.dom.Element convertToDOM(@NotNull Element e) {
try {
final Document d = new Document();
final Element newRoot = new Element(e.getName());
final List attributes = e.getAttributes();
for (Object o : attributes) {
Attribute attr = (Attribute)o;
newRoot.setAttribute(attr.getName(), attr.getValue(), attr.getNamespace());
}
d.addContent(newRoot);
newRoot.addContent(e.cloneContent());
return new DOMOutputter().output(d).getDocumentElement();
}
catch (JDOMException e1) {
throw new RuntimeException(e1);
}
}
public static Element convertFromDOM(org.w3c.dom.Element e) {
return new DOMBuilder().build(e);
}
public static String getValue(Object node) {
if (node instanceof Content) {
Content content = (Content)node;
return content.getValue();
}
else if (node instanceof Attribute) {
Attribute attribute = (Attribute)node;
return attribute.getValue();
}
else {
throw new IllegalArgumentException("Wrong node: " + node);
}
}
}