code: /*******************************************************************************
   * Copyright (c) 2009-2014 Richard Eckart de Castilho.
   *
   * 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.
   * 
   * Contributors:
   *     Richard Eckart de Castilho - initial API and implementation
   ******************************************************************************/
  package org.annolab.tt4j;
  
  import static java.io.File.separator;
  
  import java.io.Closeable;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStreamReader;
  import java.io.OutputStreamWriter;
  import java.io.Reader;
  import java.io.Writer;
  import java.nio.CharBuffer;
  import java.text.BreakIterator;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Locale;
  
  /**
   * Utility functions.
   *
   * @author Richard Eckart de Castilho
   */
  public final
  class Util
  {
  	private
  	Util()
  	{
  		// No instances
  	}
  
  	/**
  	 * Get the search paths for a model or executable. Using the {@code aSubPath}
  	 * argument, executables and models can be searched for in different
  	 * locations, e.g. executables in {@literal executable} and models in
  	 * {@literal models}.
  	 * 
  	 * <p>The returned list contains the additional search paths, the value of the
  	 * system property {@literal treetagger.home} and the
  	 * environment variables {@literal TREETAGGER_HOME} and {@literal TAGDIR}
  	 * in this order.</p>
  	 *
  	 * @param aAdditionalPaths additional paths to search in.
  	 * @param aSubPath search in the given sub-directory of the search paths.
  	 * @return a list of search paths.
  	 */
  	public static
  	List<String> getSearchPaths(
  			final List<String> aAdditionalPaths,
  			final String aSubPath)
  	{
  		final List<String> paths = new ArrayList<String>();
  		paths.addAll(aAdditionalPaths);
  		if (System.getProperty("treetagger.home") != null) {
  			paths.add(System.getProperty("treetagger.home")+separator+aSubPath);
  		}
  		if (System.getenv("TREETAGGER_HOME") != null) {
  			paths.add(System.getenv("TREETAGGER_HOME")+separator+aSubPath);
  		}
  		if (System.getenv("TAGDIR") != null) {
  			paths.add(System.getenv("TAGDIR")+separator+aSubPath);
  		}
  //		String path = System.getenv("PATH");
  //		if (path != null) {
  //			paths.addAll(asList(path.split(File.pathSeparator)));
  //		}
  		return paths;
  	}
  
      /**
       * Join the given strings into a single string separated by the given
       * separator.
       *
       * @param aStrings strings to join.
       * @param aSeparator a separator.
       * @return the joined string.
       */
      public static
  	String join(
  			final String[] aStrings,
  			final String aSeparator)
  	{
  		final StringBuilder sb = new StringBuilder();
  
  		for (int i = 0; i < aStrings.length; i++) {
  			sb.append(aStrings[i]);
  			if (i < aStrings.length - 1) {
  				sb.append(aSeparator);
  			}
  		}
  
  		return sb.toString();
  	}
  
      /**
       * Join the given strings into a single string separated by the given
       * separator.
       *
       * @param aStrings strings to join.
       * @param aSeparator a separator.
       * @return the joined string.
       */
      public static
  	String join(
  			final Collection<String> aStrings,
  			final String aSeparator)
  	{
  		final StringBuilder sb = new StringBuilder();
  
  		Iterator<String> i = aStrings.iterator();
  		while (i.hasNext()) {
  			sb.append(i.next());
  			if (i.hasNext()) {
  				sb.append(aSeparator);
  			}
  		}
  
  		return sb.toString();
  	}
  
      /**
       * Checks if a token returned by TreeTagger corresponds to the token sent to TreeTagger. This
       * method does not check for strict equality, because if TreeTagger does not know a character,
       * it will return "?" instead. So this method interprets an "?" returned by TreeTagger as a
       * single-character wildcard.
       *
       * @param tokenSent token sent to TreeTagger
       * @param tokenReturned token returned from TreeTagger
       * @return if the token returned matches the token sent.
       */
      public static
      boolean matches(
      		final String tokenSent,
      		final String tokenReturned)
      {
      	if (tokenSent == null && tokenReturned == null) {
      		return true;
      	}
      	if (tokenSent == null || tokenReturned == null) {
      		return false;
      	}
      	if (tokenSent.length() != tokenReturned.length()) {
      		return false;
      	}
      	for (int i = 0; i < tokenSent.length(); i ++) {
      		if (tokenReturned.charAt(i) == '?') {
      			continue;
      		}
      		if (tokenSent.charAt(i) == tokenReturned.charAt(i)) {
      			continue;
      		}
      		return false;
      	}
      	return true;
      }
  
      /**
       * Close the given {@link Closeable}.
       *
       * @param aClosable a closable object.
       */
      public static
      void close(
      		final Closeable aClosable)
      {
      	if (aClosable != null) {
  	    	try {
  	    		aClosable.close();
  	    	}
  	    	catch (final IOException e) {
  	    		// Ignore
  	    	}
      	}
      }
      
      /**
       * For tests only.
       * 
       * @param aFile the file.
       * @param aEncoding the encoding.
       * @return the file content.
       * @throws IOException if an I/O error occurs.
       */
      protected static
      String readFile(
      		final File aFile, 
      		final String aEncoding)
      throws IOException
      {
      	Reader reader = null;
      	try {
          	StringBuilder sb = new StringBuilder();
          	CharBuffer buffer = CharBuffer.allocate(65535);
      		reader = new InputStreamReader(new FileInputStream(aFile), aEncoding);
      		while (reader.ready()) {
      			reader.read(buffer);
      			buffer.flip();
      			sb.append(buffer.toString());
      		}
      		return sb.toString();
      	}
      	finally {
      		close(reader);
      	}
      }
  
      /**
       * For tests only.
       * 
       * @param aText the text to write.
       * @param aFile the file to write to.
       * @param aEncoding the encoding to use.
       * @throws IOException if an I/O error occurs.
       */
      protected static
      void writeFile(
      		final String aText, 
      		final File aFile, 
      		final String aEncoding)
      throws IOException
      {
      	Writer writer = null;
      	try {
      		aFile.getParentFile().mkdirs();
          	writer = new OutputStreamWriter(new FileOutputStream(aFile), aEncoding);
          	writer.write(aText);
      	}
      	finally {
      		close(writer);
      	}
      }
  
      /**
       * For tests only.
       * 
       * @param aText the text to write.
       * @param aLocale the locale to use for tokenization.
       * @return the tokens.
       */
      protected static
      String[] tokenize(
      		final String aText, 
      		final Locale aLocale)
      {
      	List<String> tokens = new ArrayList<String>();
      	BreakIterator bi = BreakIterator.getWordInstance(aLocale);
      	bi.setText(aText);
      	int begin = 0;
      	while (bi.next() != BreakIterator.DONE) {
      		tokens.add(aText.substring(begin, bi.current()));
      		begin = bi.current();
      	}
      	return tokens.toArray(new String[tokens.size()]);
      }
  }
  