001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 * Created on Sep 14, 2011
021 * Author: Amr ALHOSSARY, Richard Adams
022 *
023 */
024package org.biojava.nbio.core.util;
025
026import java.io.BufferedReader;
027import java.io.ByteArrayInputStream;
028import java.io.IOException;
029import java.io.InputStream;
030import java.io.InputStreamReader;
031import java.util.Collection;
032import java.util.Scanner;
033import java.util.stream.Collectors;
034
035import javax.xml.parsers.DocumentBuilder;
036import javax.xml.parsers.DocumentBuilderFactory;
037import javax.xml.parsers.ParserConfigurationException;
038
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041import org.w3c.dom.Document;
042import org.w3c.dom.DocumentType;
043import org.w3c.dom.NamedNodeMap;
044import org.w3c.dom.Node;
045import org.xml.sax.SAXException;
046
047
048/**
049 * A utility class for common {@link String} manipulation tasks.
050 * All functions are static methods.
051 *
052 * @author Amr ALHOSSARY
053 * @author Richard Adams
054 */
055public class StringManipulationHelper  {
056
057        private final static Logger logger = LoggerFactory.getLogger(StringManipulationHelper.class);
058
059        /**
060         * we are using Unix endline here, since this is used for testing XML and it
061         * is part of the XML recommendations: <a href
062         * ="http://www.w3.org/TR/REC-xml/#sec-line-ends"
063         * >http://www.w3.org/TR/REC-xml/#sec-line-ends</a>
064         */
065        private static final String UNIX_NEWLINE = "\n";
066
067        private StringManipulationHelper() {
068                // to prevent instantiation
069        }
070
071        /**
072         * Converts an InputStream of text to a String, closing the stream
073         * before returning.
074         * <ul>
075         * <li> Newlines are converted to Unix newlines (\n)
076         * <li> Default charset encoding is used to read the stream.
077         * <li> Any IOException reading the stream is 'squashed' and not made
078         *   available to caller
079         * <li> An additional newline is appended at the end of the string.
080         * <ul>
081         * @author andreas
082         * @param stream
083         * @return a possibly empty but non-null String
084         */
085        public static String convertStreamToString(InputStream stream) {
086                BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
087                StringBuilder sb = new StringBuilder();
088
089                String line = null;
090                try {
091                        while ((line = reader.readLine()) != null) {
092                            sb.append(line).append(UNIX_NEWLINE);
093                        }
094                } catch (IOException e) {
095                        // logger.error("Exception: ", e);
096                } finally {
097                        try {
098                                stream.close();
099                        } catch (IOException e) {
100                                logger.error("Exception: ", e);
101                        }
102                }
103                return sb.toString();
104        }
105
106        /**
107         * Compares two strings in a case-sensitive manner for equality, line by line, ignoring any difference
108         * of end line delimiters contained within the 2 Strings.
109         * <br/>
110         * This method should
111         * be used if and only if two Strings are considered identical when all nodes
112         * are identical including their relative order. Generally useful when
113         * asserting identity of <b>automatically regenerated</b> XML or PDB.
114         *
115         * @param expected
116         * @param actual
117         */
118        public static boolean equalsToIgnoreEndline(String expected, String actual) {
119                if (expected == null && actual == null) {
120                        return true;
121                }
122                if (expected != null ^ actual != null) {
123                        return false;
124                }
125                Scanner scanner1 = new Scanner(expected);
126                Scanner scanner2 = new Scanner(actual);
127                String line1, line2;
128                while (scanner1.hasNextLine()) {
129                        line1 = scanner1.nextLine();
130                        if(scanner2.hasNextLine()) {
131                                line2 = scanner2.nextLine();
132                                if (! line1.equals(line2)) {
133                                        closeScanners(scanner1, scanner2);
134                                        return false;
135                                }
136                        } else {
137                                closeScanners(scanner1, scanner2);
138                                return false;
139                        }
140                }
141                if (scanner2.hasNextLine()) {
142                        closeScanners(scanner1, scanner2);
143                        return false;
144                }
145
146                closeScanners(scanner1, scanner2);
147                return true;
148        }
149
150        private static void closeScanners(Scanner s1, Scanner s2) {
151                s1.close();
152                s2.close();
153        }
154
155        /**
156         * This method is not implemented or used, never returns true
157         * and should probably be removed.
158         * @param expected
159         * @param actual
160         * @return
161         * @throws UnsupportedOperationException in most cases
162         */
163        public static boolean equalsToXml(String expected, String actual) {
164                Document expectedDocument=null;
165                Document actualDocument=null;
166                try {
167                        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
168                        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
169                        expectedDocument = documentBuilder.parse(new ByteArrayInputStream(expected.getBytes()));
170                        actualDocument = documentBuilder.parse(new ByteArrayInputStream(actual.getBytes()));
171                } catch (ParserConfigurationException e) {
172                        logger.error("Exception: ", e);
173                        throw new RuntimeException("Couldn't Parse XML", e);
174                } catch (SAXException e) {
175                        logger.error("Exception: ", e);
176                        throw new RuntimeException("Couldn't Parse XML", e);
177                } catch (IOException e) {
178                        logger.error("Exception: ", e);
179                        throw new RuntimeException("Couldn't Parse XML", e);
180                }
181                final DocumentType doctype1 = expectedDocument.getDoctype();
182                final DocumentType doctype2 = actualDocument.getDoctype();
183                if (doctype1==null ^ doctype2 == null) {
184                        return false;
185                }else if (doctype1!= null /*&& doctype2 != null*/) {
186                        NamedNodeMap expectedNotations = doctype1.getNotations();
187                        NamedNodeMap actualNotations = doctype2.getNotations();
188                        if (expectedNotations.getLength() == actualNotations.getLength()) {
189                                for (int i = 0; i < expectedNotations.getLength(); i++) {
190                                        Node node= expectedNotations.item(i);
191                                        node.isEqualNode(null);
192                                }
193                        }else{
194                                return false;
195                        }
196
197                }
198
199                throw new UnsupportedOperationException("not yet implemented");
200        }
201
202        /**
203         * Adds padding to left of supplied string
204         * @param s The String to pad
205         * @param n an integer >= 1
206         * @return The left-padded string. 
207         * @throws IllegalArgumentException if n <= 0
208         */
209        public static String padLeft(String s, int n) {
210                validatePadding(n);
211            return String.format("%1$" + n + "s", s);
212        }
213
214        /**
215         * Adds padding to right of supplied string
216         * @param s The String to pad
217         * @param n an integer >= 1
218         * @return The right-padded string. 
219         * @throws IllegalArgumentException if n <= 0
220         */
221        public static String padRight(String s, int n) {
222                validatePadding(n);
223            return String.format("%1$-" + n + "s", s);
224        }
225
226        private static void validatePadding(int n) {
227                if (n <=0 ) {
228                        throw new IllegalArgumentException("padding must be >= 1");
229                }
230        }
231
232        /**
233         * Joins Strings together with a delimiter to a single
234         * @param s An {@link Iterable} of Strings
235         * @param delimiter
236         * @return
237         */
238        public static String join(Collection<String> s, String delimiter) {
239                if (s==null) return "";
240                return s.stream().collect(Collectors.joining(delimiter));
241        }
242
243}