save datasources in separate zipped xml
[fedora-idea.git] / util / src / com / intellij / openapi / util / JDOMUtil.java
blobe322022955da4ee0cc5d3c35c3cd114dfee99118
1 /*
2 * Copyright 2000-2007 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.openapi.util;
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.util.ArrayUtil;
20 import com.intellij.util.containers.StringInterner;
21 import com.intellij.util.io.URLUtil;
22 import com.intellij.util.io.fs.IFile;
23 import com.intellij.util.text.CharArrayUtil;
24 import com.intellij.util.text.CharSequenceReader;
25 import org.jdom.*;
26 import org.jdom.filter.Filter;
27 import org.jdom.input.DOMBuilder;
28 import org.jdom.input.SAXBuilder;
29 import org.jdom.output.DOMOutputter;
30 import org.jdom.output.Format;
31 import org.jdom.output.XMLOutputter;
32 import org.jetbrains.annotations.NonNls;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35 import org.xml.sax.EntityResolver;
36 import org.xml.sax.InputSource;
38 import java.io.*;
39 import java.net.URL;
40 import java.util.ArrayList;
41 import java.util.Iterator;
42 import java.util.List;
44 /**
45 * @author mike
47 @SuppressWarnings({"HardCodedStringLiteral"})
48 public class JDOMUtil {
49 private static final ThreadLocal<SAXBuilder> ourSaxBuilder = new ThreadLocal<SAXBuilder>(){
50 protected SAXBuilder initialValue() {
51 SAXBuilder saxBuilder = new SAXBuilder();
52 saxBuilder.setEntityResolver(new EntityResolver() {
53 public InputSource resolveEntity(String publicId, String systemId) {
54 return new InputSource(new CharArrayReader(ArrayUtil.EMPTY_CHAR_ARRAY));
56 });
57 return saxBuilder;
61 private static class LoggerHolder {
62 private static final Logger ourLogger = Logger.getInstance("#com.intellij.openapi.util.JDOMUtil");
65 private static Logger getLogger() {
66 return LoggerHolder.ourLogger;
69 private static final String ENCODING = "UTF-8";
71 public static boolean areElementsEqual(Element e1, Element e2) {
72 if (e1 == null && e2 == null) return true;
73 if (e1 == null) return false;
75 return attListsEqual(e1.getAttributes(), e2.getAttributes()) &&
76 contentListsEqual(e1.getContent(CONTENT_FILTER), e2.getContent(CONTENT_FILTER));
79 private static final EmptyTextFilter CONTENT_FILTER = new EmptyTextFilter();
81 public static int getTreeHash(final Element root) {
82 return addToHash(0, root);
85 public static int getTreeHash(Document document) {
86 return getTreeHash(document.getRootElement());
89 private static int addToHash(int i, final Element element) {
90 i = addToHash(i, element.getName());
91 i = addToHash(i, element.getText());
93 final List list = element.getAttributes();
94 for (Object aList : list) {
95 Attribute attribute = (Attribute)aList;
96 i = addToHash(i, attribute);
99 //noinspection unchecked
100 final List<Element> children = element.getChildren();
101 for (Element child : children) { //iterator is used here which is more efficient than get(index)
102 i = addToHash(i, child);
105 return i;
108 private static int addToHash(int i, final Attribute attribute) {
109 i = addToHash(i, attribute.getName());
110 i = addToHash(i, attribute.getValue());
111 return i;
114 private static int addToHash(final int i, final String s) {
115 return i * 31 + s.hashCode();
118 @SuppressWarnings({"unchecked"})
119 @NotNull
120 public static Object[] getChildNodesWithAttrs(final Element e) {
121 ArrayList<Object> result = new ArrayList<Object>();
122 result.addAll(e.getContent());
123 result.addAll(e.getAttributes());
124 return ArrayUtil.toObjectArray(result);
127 @SuppressWarnings({"unchecked"})
128 @NotNull
129 public static Content[] getContent(final Element m) {
130 final List list = m.getContent();
131 return (Content[])list.toArray(new Content[list.size()]);
134 @SuppressWarnings({"unchecked"})
135 @NotNull
136 public static Element[] getElements(final Element m) {
137 final List list = m.getChildren();
138 return (Element[])list.toArray(new Element[list.size()]);
141 @NotNull
142 public static String concatTextNodesValues(final Object[] nodes) {
143 StringBuilder result = new StringBuilder();
144 for (Object node : nodes) {
145 result.append(((Content)node).getValue());
147 return result.toString();
150 public static void addContent(final Element targetElement, final Object node) {
151 if (node instanceof Content) {
152 Content content = (Content)node;
153 targetElement.addContent(content);
155 else if (node instanceof List) {
156 List list = (List)node;
157 targetElement.addContent(list);
159 else {
160 throw new IllegalArgumentException("Wrong node: " + node);
164 public static void internElement(final Element element, final StringInterner interner) {
165 element.setName(intern(interner, element.getName()));
167 final List attributes = element.getAttributes();
168 for (Object o : attributes) {
169 Attribute attr = (Attribute)o;
170 attr.setName(intern(interner, attr.getName()));
171 attr.setValue(intern(interner, attr.getValue()));
174 final List content = element.getContent();
175 for (Object o : content) {
176 if (o instanceof Element) {
177 Element e = (Element)o;
178 internElement(e, interner);
180 else if (o instanceof Text) {
181 Text text = (Text)o;
182 text.setText(intern(interner, text.getText()));
184 else if (o instanceof Comment) {
185 Comment comment = (Comment)o;
186 comment.setText(intern(interner, comment.getText()));
188 else {
189 throw new IllegalArgumentException("Wrong node: " + o);
194 @NotNull
195 private static String intern(final StringInterner interner, final String s) {
196 synchronized (interner) {
197 return interner.intern(s);
201 @NotNull
202 public static String legalizeText(@NotNull final String str) {
203 StringReader reader = new StringReader(str);
204 StringBuilder result = new StringBuilder();
206 while(true) {
207 //noinspection EmptyCatchBlock
208 try {
209 int each = reader.read();
210 if (each == -1) break;
212 if (Verifier.isXMLCharacter(each)) {
213 result.append((char)each);
214 } else {
215 result.append("0x").append(Long.toHexString(each).toUpperCase());
218 catch (IOException e) {
222 return result.toString().replaceAll("<", "&lt;").replaceAll(">", "&gt;");
226 private static class EmptyTextFilter implements Filter {
227 public boolean matches(Object obj) {
228 if (obj instanceof Text) {
229 final Text t = (Text)obj;
230 return !CharArrayUtil.containsOnlyWhiteSpaces(t.getText());
232 return true;
236 private static boolean contentListsEqual(final List c1, final List c2) {
237 if (c1 == null && c2 == null) return true;
238 if (c1 == null) return false;
240 Iterator l1 = c1.listIterator();
241 Iterator l2 = c2.listIterator();
242 while (l1.hasNext() && l2.hasNext()) {
243 if (!contentsEqual((Content)l1.next(), (Content)l2.next())) {
244 return false;
248 return l1.hasNext() == l2.hasNext();
251 private static boolean contentsEqual(Content c1, Content c2) {
252 if (!(c1 instanceof Element) && !(c2 instanceof Element)) {
253 return c1.getValue().equals(c2.getValue());
256 return c1 instanceof Element && c2 instanceof Element && areElementsEqual((Element)c1, (Element)c2);
260 private static boolean attListsEqual(List a1, List a2) {
261 if (a1.size() != a2.size()) return false;
262 for (int i = 0; i < a1.size(); i++) {
263 if (!attEqual((Attribute)a1.get(i), (Attribute)a2.get(i))) return false;
265 return true;
268 private static boolean attEqual(Attribute a1, Attribute a2) {
269 return a1.getName().equals(a2.getName()) && a1.getValue().equals(a2.getValue());
272 public static boolean areDocumentsEqual(Document d1, Document d2) {
273 if(d1.hasRootElement() != d2.hasRootElement()) return false;
275 if(!d1.hasRootElement()) return true;
277 CharArrayWriter w1 = new CharArrayWriter();
278 CharArrayWriter w2 = new CharArrayWriter();
280 try {
281 writeDocument(d1, w1, "\n");
282 writeDocument(d2, w2, "\n");
284 catch (IOException e) {
285 getLogger().error(e);
288 return w1.size() == w2.size() && w1.toString().equals(w2.toString());
292 @NotNull
293 public static Document loadDocument(char[] chars, int length) throws IOException, JDOMException {
294 SAXBuilder builder = ourSaxBuilder.get();
295 return builder.build(new CharArrayReader(chars, 0, length));
298 @NotNull
299 public static Document loadDocument(CharSequence seq) throws IOException, JDOMException {
300 SAXBuilder builder = ourSaxBuilder.get();
301 return builder.build(new CharSequenceReader(seq));
304 @NotNull
305 public static Document loadDocument(File file) throws JDOMException, IOException {
306 return loadDocument(new BufferedInputStream(new FileInputStream(file)));
309 @NotNull
310 public static Document loadDocument(final IFile iFile) throws IOException, JDOMException {
311 return loadDocument(new BufferedInputStream(iFile.openInputStream()));
314 @NotNull
315 public static Document loadDocument(@NotNull InputStream stream) throws JDOMException, IOException {
316 SAXBuilder saxBuilder = ourSaxBuilder.get();
317 InputStreamReader reader = new InputStreamReader(stream, ENCODING);
318 try {
319 return saxBuilder.build(reader);
321 finally {
322 reader.close();
326 @NotNull
327 public static Document loadDocument(URL url) throws JDOMException, IOException {
328 return loadDocument(URLUtil.openStream(url));
331 @NotNull
332 public static Document loadResourceDocument(URL url) throws JDOMException, IOException {
333 return loadDocument(URLUtil.openResourceStream(url));
336 public static void writeDocument(Document document, String filePath, String lineSeparator) throws IOException {
337 OutputStream stream = new BufferedOutputStream(new FileOutputStream(filePath));
338 try {
339 writeDocument(document, stream, lineSeparator);
341 finally {
342 stream.close();
346 public static void writeDocument(Document document, File file, String lineSeparator) throws IOException {
347 OutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
348 try {
349 writeDocument(document, stream, lineSeparator);
351 finally {
352 stream.close();
356 public static void writeDocument(Document document, OutputStream stream, String lineSeparator) throws IOException {
357 writeDocument(document, new OutputStreamWriter(stream, ENCODING), lineSeparator);
361 @NotNull
362 public static byte[] printDocument(Document document, String lineSeparator) throws IOException {
363 CharArrayWriter writer = new CharArrayWriter();
364 writeDocument(document, writer, lineSeparator);
366 return new String(writer.toCharArray()).getBytes(ENCODING);
369 @NotNull
370 public static String writeDocument(Document document, String lineSeparator) {
371 try {
372 final StringWriter writer = new StringWriter();
373 writeDocument(document, writer, lineSeparator);
374 return writer.toString();
376 catch (IOException e) {
377 // Can't be
378 return "";
382 public static void writeElement(Element element, Writer writer, String lineSeparator) throws IOException {
383 XMLOutputter xmlOutputter = createOutputter(lineSeparator);
384 try {
385 xmlOutputter.output(element, writer);
387 catch (NullPointerException ex) {
388 getLogger().error(ex);
389 printDiagnostics(element, "");
393 @NotNull
394 public static String writeElement(Element element, String lineSeparator) throws IOException {
395 final StringWriter writer = new StringWriter();
396 writeElement(element, writer, lineSeparator);
397 return writer.toString();
400 public static void writeDocument(Document document, Writer writer, String lineSeparator) throws IOException {
401 XMLOutputter xmlOutputter = createOutputter(lineSeparator);
402 try {
403 xmlOutputter.output(document, writer);
405 catch (NullPointerException ex) {
406 getLogger().error(ex);
407 printDiagnostics(document.getRootElement(), "");
411 @NotNull
412 public static XMLOutputter createOutputter(String lineSeparator) {
413 XMLOutputter xmlOutputter = new MyXMLOutputter();
414 Format format = Format.getCompactFormat().
415 setIndent(" ").
416 setTextMode(Format.TextMode.NORMALIZE).
417 setEncoding(ENCODING).
418 setOmitEncoding(false).
419 setOmitDeclaration(false).
420 setLineSeparator(lineSeparator);
421 xmlOutputter.setFormat(format);
422 return xmlOutputter;
426 * Returns null if no escapement necessary.
428 @Nullable
429 private static String escapeChar(char c, boolean escapeLineEnds) {
430 switch (c) {
431 case '\n': return escapeLineEnds ? "&#10;" : null;
432 case '\r': return escapeLineEnds ? "&#13;" : null;
433 case '\t': return escapeLineEnds ? "&#9;" : null;
434 case '<': return "&lt;";
435 case '>': return "&gt;";
436 case '\"': return "&quot;";
437 case '&': return "&amp;";
439 return null;
442 @NotNull
443 public static String escapeText(String text) {
444 return escapeText(text, false);
447 @NotNull
448 private static String escapeText(String text, boolean escapeLineEnds) {
449 StringBuffer buffer = null;
450 for (int i = 0; i < text.length(); i++) {
451 final char ch = text.charAt(i);
452 final String quotation = escapeChar(ch, escapeLineEnds);
454 if (buffer == null) {
455 if (quotation != null) {
456 // An quotation occurred, so we'll have to use StringBuffer
457 // (allocate room for it plus a few more entities).
458 buffer = new StringBuffer(text.length() + 20);
459 // Copy previous skipped characters and fall through
460 // to pickup current character
461 buffer.append(text.substring(0, i));
462 buffer.append(quotation);
465 else {
466 if (quotation == null) {
467 buffer.append(ch);
469 else {
470 buffer.append(quotation);
474 // If there were any entities, return the escaped characters
475 // that we put in the StringBuffer. Otherwise, just return
476 // the unmodified input string.
477 return buffer == null ? text : buffer.toString();
480 @NotNull
481 public static List<Element> getChildrenFromAllNamespaces(final Element element, @NonNls final String name) {
482 final ArrayList<Element> result = new ArrayList<Element>();
483 final List children = element.getChildren();
484 for (final Object aChildren : children) {
485 Element child = (Element)aChildren;
486 if (name.equals(child.getName())) {
487 result.add(child);
490 return result;
493 public static class MyXMLOutputter extends XMLOutputter {
494 public String escapeAttributeEntities(String str) {
495 return escapeText(str, true);
498 public String escapeElementEntities(String str) {
499 return escapeText(str, false);
503 private static void printDiagnostics(Element element, String prefix) {
504 ElementInfo info = getElementInfo(element);
505 prefix = prefix + "/" + info.name;
506 if (info.hasNullAttributes) {
507 System.err.println(prefix);
510 //noinspection unchecked
511 List<Element> children = element.getChildren();
512 for (final Element child : children) {
513 printDiagnostics(child, prefix);
517 @NotNull
518 private static ElementInfo getElementInfo(Element element) {
519 ElementInfo info = new ElementInfo();
520 StringBuilder buf = new StringBuilder(element.getName());
521 List attributes = element.getAttributes();
522 if (attributes != null) {
523 int length = attributes.size();
524 if (length > 0) {
525 buf.append("[");
526 for (int idx = 0; idx < length; idx++) {
527 Attribute attr = (Attribute)attributes.get(idx);
528 if (idx != 0) {
529 buf.append(";");
531 buf.append(attr.getName());
532 buf.append("=");
533 buf.append(attr.getValue());
534 if (attr.getValue() == null) {
535 info.hasNullAttributes = true;
538 buf.append("]");
541 info.name = buf.toString();
542 return info;
545 public static void updateFileSet(File[] oldFiles, String[] newFilePaths, Document[] newFileDocuments, String lineSeparator)
546 throws IOException {
547 getLogger().assertTrue(newFilePaths.length == newFileDocuments.length);
549 ArrayList<String> writtenFilesPaths = new ArrayList<String>();
551 // check if files are writable
552 for (String newFilePath : newFilePaths) {
553 File file = new File(newFilePath);
554 if (file.exists() && !file.canWrite()) {
555 throw new IOException("File \"" + newFilePath + "\" is not writeable");
558 for (File file : oldFiles) {
559 if (file.exists() && !file.canWrite()) {
560 throw new IOException("File \"" + file.getAbsolutePath() + "\" is not writeable");
564 for (int i = 0; i < newFilePaths.length; i++) {
565 String newFilePath = newFilePaths[i];
567 writeDocument(newFileDocuments[i], newFilePath, lineSeparator);
568 writtenFilesPaths.add(newFilePath);
571 // delete files if necessary
573 outer:
574 for (File oldFile : oldFiles) {
575 String oldFilePath = oldFile.getAbsolutePath();
576 for (final String writtenFilesPath : writtenFilesPaths) {
577 if (oldFilePath.equals(writtenFilesPath)) {
578 continue outer;
581 boolean result = oldFile.delete();
582 if (!result) {
583 throw new IOException("File \"" + oldFilePath + "\" was not deleted");
588 private static class ElementInfo {
589 public String name = "";
590 public boolean hasNullAttributes = false;
593 public static org.w3c.dom.Element convertToDOM(@NotNull Element e) {
594 try {
595 final Document d = new Document();
596 final Element newRoot = new Element(e.getName());
597 final List attributes = e.getAttributes();
599 for (Object o : attributes) {
600 Attribute attr = (Attribute)o;
601 newRoot.setAttribute(attr.getName(), attr.getValue(), attr.getNamespace());
604 d.addContent(newRoot);
605 newRoot.addContent(e.cloneContent());
607 return new DOMOutputter().output(d).getDocumentElement();
609 catch (JDOMException e1) {
610 throw new RuntimeException(e1);
614 public static Element convertFromDOM(org.w3c.dom.Element e) {
615 return new DOMBuilder().build(e);
618 public static String getValue(Object node) {
619 if (node instanceof Content) {
620 Content content = (Content)node;
621 return content.getValue();
623 else if (node instanceof Attribute) {
624 Attribute attribute = (Attribute)node;
625 return attribute.getValue();
627 else {
628 throw new IllegalArgumentException("Wrong node: " + node);