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
;
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
;
40 import java
.util
.ArrayList
;
41 import java
.util
.Iterator
;
42 import java
.util
.List
;
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
));
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
);
108 private static int addToHash(int i
, final Attribute attribute
) {
109 i
= addToHash(i
, attribute
.getName());
110 i
= addToHash(i
, attribute
.getValue());
114 private static int addToHash(final int i
, final String s
) {
115 return i
* 31 + s
.hashCode();
118 @SuppressWarnings({"unchecked"})
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"})
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"})
136 public static Element
[] getElements(final Element m
) {
137 final List list
= m
.getChildren();
138 return (Element
[])list
.toArray(new Element
[list
.size()]);
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
);
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
) {
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()));
189 throw new IllegalArgumentException("Wrong node: " + o
);
195 private static String
intern(final StringInterner interner
, final String s
) {
196 synchronized (interner
) {
197 return interner
.intern(s
);
202 public static String
legalizeText(@NotNull final String str
) {
203 StringReader reader
= new StringReader(str
);
204 StringBuilder result
= new StringBuilder();
207 //noinspection EmptyCatchBlock
209 int each
= reader
.read();
210 if (each
== -1) break;
212 if (Verifier
.isXMLCharacter(each
)) {
213 result
.append((char)each
);
215 result
.append("0x").append(Long
.toHexString(each
).toUpperCase());
218 catch (IOException e
) {
222 return result
.toString().replaceAll("<", "<").replaceAll(">", ">");
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());
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())) {
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;
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();
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());
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
));
299 public static Document
loadDocument(CharSequence seq
) throws IOException
, JDOMException
{
300 SAXBuilder builder
= ourSaxBuilder
.get();
301 return builder
.build(new CharSequenceReader(seq
));
305 public static Document
loadDocument(File file
) throws JDOMException
, IOException
{
306 return loadDocument(new BufferedInputStream(new FileInputStream(file
)));
310 public static Document
loadDocument(final IFile iFile
) throws IOException
, JDOMException
{
311 return loadDocument(new BufferedInputStream(iFile
.openInputStream()));
315 public static Document
loadDocument(@NotNull InputStream stream
) throws JDOMException
, IOException
{
316 SAXBuilder saxBuilder
= ourSaxBuilder
.get();
317 InputStreamReader reader
= new InputStreamReader(stream
, ENCODING
);
319 return saxBuilder
.build(reader
);
327 public static Document
loadDocument(URL url
) throws JDOMException
, IOException
{
328 return loadDocument(URLUtil
.openStream(url
));
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
));
339 writeDocument(document
, stream
, lineSeparator
);
346 public static void writeDocument(Document document
, File file
, String lineSeparator
) throws IOException
{
347 OutputStream stream
= new BufferedOutputStream(new FileOutputStream(file
));
349 writeDocument(document
, stream
, lineSeparator
);
356 public static void writeDocument(Document document
, OutputStream stream
, String lineSeparator
) throws IOException
{
357 writeDocument(document
, new OutputStreamWriter(stream
, ENCODING
), lineSeparator
);
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
);
370 public static String
writeDocument(Document document
, String lineSeparator
) {
372 final StringWriter writer
= new StringWriter();
373 writeDocument(document
, writer
, lineSeparator
);
374 return writer
.toString();
376 catch (IOException e
) {
382 public static void writeElement(Element element
, Writer writer
, String lineSeparator
) throws IOException
{
383 XMLOutputter xmlOutputter
= createOutputter(lineSeparator
);
385 xmlOutputter
.output(element
, writer
);
387 catch (NullPointerException ex
) {
388 getLogger().error(ex
);
389 printDiagnostics(element
, "");
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
);
403 xmlOutputter
.output(document
, writer
);
405 catch (NullPointerException ex
) {
406 getLogger().error(ex
);
407 printDiagnostics(document
.getRootElement(), "");
412 public static XMLOutputter
createOutputter(String lineSeparator
) {
413 XMLOutputter xmlOutputter
= new MyXMLOutputter();
414 Format format
= Format
.getCompactFormat().
416 setTextMode(Format
.TextMode
.NORMALIZE
).
417 setEncoding(ENCODING
).
418 setOmitEncoding(false).
419 setOmitDeclaration(false).
420 setLineSeparator(lineSeparator
);
421 xmlOutputter
.setFormat(format
);
426 * Returns null if no escapement necessary.
429 private static String
escapeChar(char c
, boolean escapeLineEnds
) {
431 case '\n': return escapeLineEnds ?
" " : null;
432 case '\r': return escapeLineEnds ?
" " : null;
433 case '\t': return escapeLineEnds ?
"	" : null;
434 case '<': return "<";
435 case '>': return ">";
436 case '\"': return """;
437 case '&': return "&";
443 public static String
escapeText(String text
) {
444 return escapeText(text
, false);
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
);
466 if (quotation
== null) {
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();
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())) {
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
);
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();
526 for (int idx
= 0; idx
< length
; idx
++) {
527 Attribute attr
= (Attribute
)attributes
.get(idx
);
531 buf
.append(attr
.getName());
533 buf
.append(attr
.getValue());
534 if (attr
.getValue() == null) {
535 info
.hasNullAttributes
= true;
541 info
.name
= buf
.toString();
545 public static void updateFileSet(File
[] oldFiles
, String
[] newFilePaths
, Document
[] newFileDocuments
, String lineSeparator
)
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
574 for (File oldFile
: oldFiles
) {
575 String oldFilePath
= oldFile
.getAbsolutePath();
576 for (final String writtenFilesPath
: writtenFilesPaths
) {
577 if (oldFilePath
.equals(writtenFilesPath
)) {
581 boolean result
= oldFile
.delete();
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
) {
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();
628 throw new IllegalArgumentException("Wrong node: " + node
);