/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ /* $Id$ */ package org.apache.fop.hyphenation; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xml.sax.InputSource; /** * This class is the main entry point to the hyphenation package. * You can use only the static methods or create an instance. * * @author Carlos Villegas */ public class Hyphenator { /** logging instance */ protected static Log log = LogFactory.getLog(Hyphenator.class); private static HyphenationTreeCache hTreeCache = null; private HyphenationTree hyphenTree = null; private int remainCharCount = 2; private int pushCharCount = 2; /** Enables a dump of statistics. Note: If activated content is sent to System.out! */ private static boolean statisticsDump = false; /** * Creates a new hyphenator. * @param lang the language * @param country the country (may be null or "none") * @param leftMin the minimum number of characters before the hyphenation point * @param rightMin the minimum number of characters after the hyphenation point */ public Hyphenator(String lang, String country, int leftMin, int rightMin) { hyphenTree = getHyphenationTree(lang, country); remainCharCount = leftMin; pushCharCount = rightMin; } /** @return the default (static) hyphenation tree cache */ public static synchronized HyphenationTreeCache getHyphenationTreeCache() { if (hTreeCache == null) { hTreeCache = new HyphenationTreeCache(); } return hTreeCache; } /** * Returns a hyphenation tree for a given language and country. The hyphenation trees are * cached. * @param lang the language * @param country the country (may be null or "none") * @return the hyphenation tree */ public static HyphenationTree getHyphenationTree(String lang, String country) { return getHyphenationTree(lang, country, null); } /** * Returns a hyphenation tree for a given language and country. The hyphenation trees are * cached. * @param lang the language * @param country the country (may be null or "none") * @param resolver resolver to find the hyphenation files * @return the hyphenation tree */ public static HyphenationTree getHyphenationTree(String lang, String country, HyphenationTreeResolver resolver) { String key = HyphenationTreeCache.constructKey(lang, country); HyphenationTreeCache cache = getHyphenationTreeCache(); // See if there was an error finding this hyphenation tree before if (cache.isMissing(key)) { return null; } HyphenationTree hTree; // first try to find it in the cache hTree = getHyphenationTreeCache().getHyphenationTree(lang, country); if (hTree != null) { return hTree; } if (resolver != null) { hTree = getUserHyphenationTree(key, resolver); } if (hTree == null) { hTree = getFopHyphenationTree(key); } // put it into the pattern cache if (hTree != null) { cache.cache(key, hTree); } else { log.error("Couldn't find hyphenation pattern " + key); cache.noteMissing(key); } return hTree; } private static InputStream getResourceStream(String key) { InputStream is = null; // Try to use Context Class Loader to load the properties file. try { java.lang.reflect.Method getCCL = Thread.class.getMethod( "getContextClassLoader", new Class[0]); if (getCCL != null) { ClassLoader contextClassLoader = (ClassLoader)getCCL.invoke( Thread.currentThread(), new Object[0]); is = contextClassLoader.getResourceAsStream("hyph/" + key + ".hyp"); } } catch (Exception e) { //ignore, fallback further down } if (is == null) { is = Hyphenator.class.getResourceAsStream("/hyph/" + key + ".hyp"); } return is; } private static HyphenationTree readHyphenationTree(InputStream in) { HyphenationTree hTree = null; try { ObjectInputStream ois = new ObjectInputStream(in); hTree = (HyphenationTree)ois.readObject(); } catch (IOException ioe) { log.error("I/O error while loading precompiled hyphenation pattern file", ioe); } catch (ClassNotFoundException cnfe) { log.error("Error while reading hyphenation object from file", cnfe); } return hTree; } /** * Returns a hyphenation tree. This method looks in the resources (getResourceStream) for * the hyphenation patterns. * @param key the language/country key * @return the hyphenation tree or null if it wasn't found in the resources */ public static HyphenationTree getFopHyphenationTree(String key) { HyphenationTree hTree = null; ObjectInputStream ois = null; InputStream is = null; try { is = getResourceStream(key); if (is == null) { if (key.length() == 5) { String lang = key.substring(0, 2); is = getResourceStream(lang); if (is != null) { if (log.isDebugEnabled()) { log.debug("Couldn't find hyphenation pattern '" + key + "'. Using general language pattern '" + lang + "' instead."); } } else { if (log.isDebugEnabled()) { log.debug("Couldn't find precompiled hyphenation pattern " + lang + " in resources."); } return null; } } else { if (log.isDebugEnabled()) { log.debug("Couldn't find precompiled hyphenation pattern " + key + " in resources"); } return null; } } hTree = readHyphenationTree(is); } finally { IOUtils.closeQuietly(ois); } return hTree; } /** * Load tree from serialized file or xml file * using configuration settings * @param key language key for the requested hyphenation file * @param hyphenDir base directory to find hyphenation files in * @return the requested HypenationTree or null if it is not available */ public static HyphenationTree getUserHyphenationTree(String key, String hyphenDir) { final File baseDir = new File(hyphenDir); HyphenationTreeResolver resolver = new HyphenationTreeResolver() { public Source resolve(String href) { File f = new File(baseDir, href); return new StreamSource(f); } }; return getUserHyphenationTree(key, resolver); } /** * Load tree from serialized file or xml file * using configuration settings * @param key language key for the requested hyphenation file * @param resolver resolver to find the hyphenation files * @return the requested HypenationTree or null if it is not available */ public static HyphenationTree getUserHyphenationTree(String key, HyphenationTreeResolver resolver) { HyphenationTree hTree = null; // I use here the following convention. The file name specified in // the configuration is taken as the base name. First we try // name + ".hyp" assuming a serialized HyphenationTree. If that fails // we try name + ".xml", assumming a raw hyphenation pattern file. // first try serialized object String name = key + ".hyp"; Source source = resolver.resolve(name); if (source != null) { try { InputStream in = null; if (source instanceof StreamSource) { in = ((StreamSource) source).getInputStream(); } if (in == null) { if (source.getSystemId() != null) { in = new java.net.URL(source.getSystemId()).openStream(); } else { throw new UnsupportedOperationException("Cannot load hyphenation pattern file" + " with the supplied Source object: " + source); } } in = new BufferedInputStream(in); try { hTree = readHyphenationTree(in); } finally { IOUtils.closeQuietly(in); } return hTree; } catch (IOException ioe) { if (log.isDebugEnabled()) { log.debug("I/O problem while trying to load " + name, ioe); } } } // try the raw XML file name = key + ".xml"; source = resolver.resolve(name); if (source != null) { hTree = new HyphenationTree(); try { InputStream in = null; if (source instanceof StreamSource) { in = ((StreamSource) source).getInputStream(); } if (in == null) { if (source.getSystemId() != null) { in = new java.net.URL(source.getSystemId()).openStream(); } else { throw new UnsupportedOperationException( "Cannot load hyphenation pattern file" + " with the supplied Source object: " + source); } } if (!(in instanceof BufferedInputStream)) { in = new BufferedInputStream(in); } try { InputSource src = new InputSource(in); src.setSystemId(source.getSystemId()); hTree.loadPatterns(src); } finally { IOUtils.closeQuietly(in); } if (statisticsDump) { System.out.println("Stats: "); hTree.printStats(); } return hTree; } catch (HyphenationException ex) { log.error("Can't load user patterns from XML file " + source.getSystemId() + ": " + ex.getMessage()); return null; } catch (IOException ioe) { if (log.isDebugEnabled()) { log.debug("I/O problem while trying to load " + name, ioe); } return null; } } else { if (log.isDebugEnabled()) { log.debug("Could not load user hyphenation file for '" + key + "'."); } return null; } } /** * Hyphenates a word. * @param lang the language * @param country the optional country code (may be null or "none") * @param resolver resolver to find the hyphenation files * @param word the word to hyphenate * @param leftMin the minimum number of characters before the hyphenation point * @param rightMin the minimum number of characters after the hyphenation point * @return the hyphenation result */ public static Hyphenation hyphenate(String lang, String country, HyphenationTreeResolver resolver, String word, int leftMin, int rightMin) { HyphenationTree hTree = getHyphenationTree(lang, country, resolver); if (hTree == null) { return null; } return hTree.hyphenate(word, leftMin, rightMin); } /** * Hyphenates a word. * @param lang the language * @param country the optional country code (may be null or "none") * @param word the word to hyphenate * @param leftMin the minimum number of characters before the hyphenation point * @param rightMin the minimum number of characters after the hyphenation point * @return the hyphenation result */ public static Hyphenation hyphenate(String lang, String country, String word, int leftMin, int rightMin) { return hyphenate(lang, country, null, word, leftMin, rightMin); } /** * Hyphenates a word. * @param lang the language * @param country the optional country code (may be null or "none") * @param resolver resolver to find the hyphenation files * @param word the word to hyphenate * @param offset the offset of the first character in the "word" character array * @param len the length of the word * @param leftMin the minimum number of characters before the hyphenation point * @param rightMin the minimum number of characters after the hyphenation point * @return the hyphenation result */ public static Hyphenation hyphenate(String lang, String country, HyphenationTreeResolver resolver, char[] word, int offset, int len, int leftMin, int rightMin) { HyphenationTree hTree = getHyphenationTree(lang, country, resolver); if (hTree == null) { return null; } return hTree.hyphenate(word, offset, len, leftMin, rightMin); } /** * Hyphenates a word. * @param lang the language * @param country the optional country code (may be null or "none") * @param word the word to hyphenate * @param offset the offset of the first character in the "word" character array * @param len the length of the word * @param leftMin the minimum number of characters before the hyphenation point * @param rightMin the minimum number of characters after the hyphenation point * @return the hyphenation result */ public static Hyphenation hyphenate(String lang, String country, char[] word, int offset, int len, int leftMin, int rightMin) { return hyphenate(lang, country, null, word, offset, len, leftMin, rightMin); } /** * Sets the minimum number of characters before the hyphenation point * @param min the number of characters */ public void setMinRemainCharCount(int min) { remainCharCount = min; } /** * Sets the minimum number of characters after the hyphenation point * @param min the number of characters */ public void setMinPushCharCount(int min) { pushCharCount = min; } /** * Sets the language and country for the hyphenation process. * @param lang the language * @param country the country (may be null or "none") */ public void setLanguage(String lang, String country) { hyphenTree = getHyphenationTree(lang, country); } /** * Hyphenates a word. * @param word the word to hyphenate * @param offset the offset of the first character in the "word" character array * @param len the length of the word * @return the hyphenation result */ public Hyphenation hyphenate(char[] word, int offset, int len) { if (hyphenTree == null) { return null; } return hyphenTree.hyphenate(word, offset, len, remainCharCount, pushCharCount); } /** * Hyphenates a word. * @param word the word to hyphenate * @return the hyphenation result */ public Hyphenation hyphenate(String word) { if (hyphenTree == null) { return null; } return hyphenTree.hyphenate(word, remainCharCount, pushCharCount); } }