code: /*
   * 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.xml.serializer;
  
  import java.io.BufferedInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.security.AccessController;
  import java.security.PrivilegedAction;
  import java.util.Enumeration;
  import java.util.Properties;
  
  import javax.xml.transform.OutputKeys;
  
  import org.apache.xml.serializer.utils.MsgKey;
  import org.apache.xml.serializer.utils.Utils;
  import org.apache.xml.serializer.utils.WrappedRuntimeException;
  
  /**
   * This class is a factory to generate a set of default properties
   * of key/value pairs that are used to create a serializer through the
   * factory {@link SerializerFactory SerilizerFactory}. 
   * The properties generated by this factory
   * may be modified to non-default values before the SerializerFactory is used to
   * create a Serializer.
   * <p>
   * The given output types supported are "xml", "text", and "html". 
   * These type strings can be obtained from the 
   * {@link Method Method} class in this package.
   * <p>
   * Other constants defined in this class are the non-standard property keys
   * that can be used to set non-standard property values on a java.util.Properties object
   * that is used to create or configure a serializer. Here are the non-standard keys:
   * <ul>
   * <li> <b>S_KEY_INDENT_AMOUNT </b> -
   * The non-standard property key to use to set the indentation amount.
   * The "indent" key needs to have a value of "yes", and this
   * properties value is a the number of whitespaces to indent by per
   * indentation level.
   * 
   * <li> <b>S_KEY_CONTENT_HANDLER </b> -
   * This non-standard property key is used to set the name of the fully qualified 
   * Java class that implements the ContentHandler interface. 
   * The output of the serializer will be SAX events sent to this an
   * object of this class.
   * 
   * <li> <b>S_KEY_ENTITIES </b> -
   * This non-standard property key is used to specify the name of the property file
   * that specifies character to entity reference mappings. A line in such a
   * file is has the name of the entity and the numeric (base 10) value
   * of the corresponding character, like this one: <br> quot=34 <br>
   * 
   * <li> <b>S_USE_URL_ESCAPING </b> -
   * This non-standard property key is used to set a value of "yes" if the href values for HTML serialization should
   *  use %xx escaping.
   * 
   * <li> <b>S_OMIT_META_TAG </b> -
   * This non-standard property key is used to set a value of "yes" if the META tag should be omitted where it would
   *  otherwise be supplied.
   * </ul>
   * 
   * @see SerializerFactory
   * @see Method
   * @see Serializer
   */
  public final class OutputPropertiesFactory
  {
      /** S_BUILTIN_EXTENSIONS_URL is a mnemonic for the XML Namespace 
       *(http://xml.apache.org/xalan) predefined to signify Xalan's
       * built-in XSLT Extensions. When used in stylesheets, this is often 
       * bound to the "xalan:" prefix.
       */
      private static final String 
        S_BUILTIN_EXTENSIONS_URL = "http://xml.apache.org/xalan"; 
  
      /**
       * The old built-in extension url. It is still supported for
       * backward compatibility.
       */
      private static final String 
        S_BUILTIN_OLD_EXTENSIONS_URL = "http://xml.apache.org/xslt"; 
        
      //************************************************************
      //*  PUBLIC CONSTANTS
      //************************************************************
      /** 
       * This is not a public API.
       * This is the built-in extensions namespace, 
       * reexpressed in {namespaceURI} syntax
       * suitable for prepending to a localname to produce a "universal
       * name".
       */
      public static final String S_BUILTIN_EXTENSIONS_UNIVERSAL =
          "{" + S_BUILTIN_EXTENSIONS_URL + "}";
  
      // Some special Xalan keys.
  
      /** 
       * The non-standard property key to use to set the
       * number of whitepaces to indent by, per indentation level,
       * if indent="yes".
       */
      public static final String S_KEY_INDENT_AMOUNT =
          S_BUILTIN_EXTENSIONS_UNIVERSAL + "indent-amount";
          
      /** 
       * The non-standard property key to use to set the
       * characters to write out as at the end of a line,
       * rather than the default ones from the runtime.
       */
      public static final String S_KEY_LINE_SEPARATOR =
          S_BUILTIN_EXTENSIONS_UNIVERSAL + "line-separator";        
  
      /** This non-standard property key is used to set the name of the fully qualified 
       * Java class that implements the ContentHandler interface. 
       * Fully qualified name of class with a default constructor that
       *  implements the ContentHandler interface, where the result tree events
       *  will be sent to.
       */
  
      public static final String S_KEY_CONTENT_HANDLER =
          S_BUILTIN_EXTENSIONS_UNIVERSAL + "content-handler";
  
      /**
       * This non-standard property key is used to specify the name of the property file
       * that specifies character to entity reference mappings.
       */
      public static final String S_KEY_ENTITIES =
          S_BUILTIN_EXTENSIONS_UNIVERSAL + "entities";
  
      /** 
       * This non-standard property key is used to set a value of "yes" if the href values for HTML serialization should
       *  use %xx escaping. */
      public static final String S_USE_URL_ESCAPING =
          S_BUILTIN_EXTENSIONS_UNIVERSAL + "use-url-escaping";
  
      /** 
       * This non-standard property key is used to set a value of "yes" if the META tag should be omitted where it would
       *  otherwise be supplied.
       */
      public static final String S_OMIT_META_TAG =
          S_BUILTIN_EXTENSIONS_UNIVERSAL + "omit-meta-tag";
  
      /**
       * The old built-in extension namespace, this is not a public API.
       */
      public static final String S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL =
          "{" + S_BUILTIN_OLD_EXTENSIONS_URL + "}";
  
      /**
       * This is not a public API, it is only public because it is used
       * by outside of this package,
       * it is the length of the old built-in extension namespace.
       */
      public static final int S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL_LEN =
          S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL.length();
  
      //************************************************************
      //*  PRIVATE CONSTANTS
      //************************************************************
  
      private static final String S_XSLT_PREFIX = "xslt.output.";
      private static final int S_XSLT_PREFIX_LEN = S_XSLT_PREFIX.length();
      private static final String S_XALAN_PREFIX = "org.apache.xslt.";
      private static final int S_XALAN_PREFIX_LEN = S_XALAN_PREFIX.length();
  
      /** Synchronization object for lazy initialization of the above tables. */
      private static Integer m_synch_object = new Integer(1);
  
      /** the directory in which the various method property files are located */
      private static final String PROP_DIR = SerializerBase.PKG_PATH+'/';
      /** property file for default XML properties */
      private static final String PROP_FILE_XML = "output_xml.properties";
      /** property file for default TEXT properties */
      private static final String PROP_FILE_TEXT = "output_text.properties";
      /** property file for default HTML properties */
      private static final String PROP_FILE_HTML = "output_html.properties";
      /** property file for default UNKNOWN (Either XML or HTML, to be determined later) properties */
      private static final String PROP_FILE_UNKNOWN = "output_unknown.properties";
  
      //************************************************************
      //*  PRIVATE STATIC FIELDS
      //************************************************************
  
      /** The default properties of all output files. */
      private static Properties m_xml_properties = null;
  
      /** The default properties when method="html". */
      private static Properties m_html_properties = null;
  
      /** The default properties when method="text". */
      private static Properties m_text_properties = null;
  
      /** The properties when method="" for the "unknown" wrapper */
      private static Properties m_unknown_properties = null;
  
      private static final Class
          ACCESS_CONTROLLER_CLASS = findAccessControllerClass();
  
      private static Class findAccessControllerClass() {
          try
          {
              // This Class was introduced in JDK 1.2. With the re-architecture of
              // security mechanism ( starting in JDK 1.2 ), we have option of
              // giving privileges to certain part of code using doPrivileged block.
              // In JDK1.1.X applications won't be having security manager and if
              // there is security manager ( in applets ), code need to be signed
              // and trusted for having access to resources.
  
              return Class.forName("java.security.AccessController");
          }
          catch (Exception e)
          {
              //User may be using older JDK ( JDK <1.2 ). Allow him/her to use it.
              // But don't try to use doPrivileged
          }
  
          return null;
      }
  
      /**
       * Creates an empty OutputProperties with the property key/value defaults specified by
       * a property file.  The method argument is used to construct a string of
       * the form output_[method].properties (for instance, output_html.properties).
       * The output_xml.properties file is always used as the base.
       * 
       * <p>Anything other than 'text', 'xml', and 'html', will
       * use the output_xml.properties file.</p>
       *
       * @param   method non-null reference to method name.
       *
       * @return Properties object that holds the defaults for the given method.
       */
      static public final Properties getDefaultMethodProperties(String method)
      {
          String fileName = null;
          Properties defaultProperties = null;
          // According to this article : Double-check locking does not work
          // http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-toolbox.html
          try
          {
              synchronized (m_synch_object)
              {
                  if (null == m_xml_properties) // double check
                  {
                      fileName = PROP_FILE_XML;
                      m_xml_properties = loadPropertiesFile(fileName, null);
                  }
              }
  
              if (method.equals(Method.XML))
              {
                  defaultProperties = m_xml_properties;
              }
              else if (method.equals(Method.HTML))
              {
                  if (null == m_html_properties) // double check
                  {
                      fileName = PROP_FILE_HTML;
                      m_html_properties =
                          loadPropertiesFile(fileName, m_xml_properties);
                  }
  
                  defaultProperties = m_html_properties;
              }
              else if (method.equals(Method.TEXT))
              {
                  if (null == m_text_properties) // double check
                  {
                      fileName = PROP_FILE_TEXT;
                      m_text_properties =
                          loadPropertiesFile(fileName, m_xml_properties);
                      if (null
                          == m_text_properties.getProperty(OutputKeys.ENCODING))
                      {
                          String mimeEncoding = Encodings.getMimeEncoding(null);
                          m_text_properties.put(
                              OutputKeys.ENCODING,
                              mimeEncoding);
                      }
                  }
  
                  defaultProperties = m_text_properties;
              }
              else if (method.equals(Method.UNKNOWN))
              {
                  if (null == m_unknown_properties) // double check
                  {
                      fileName = PROP_FILE_UNKNOWN;
                      m_unknown_properties =
                          loadPropertiesFile(fileName, m_xml_properties);
                  }
  
                  defaultProperties = m_unknown_properties;
              }
              else
              {
                  // TODO: Calculate res file from name.
                  defaultProperties = m_xml_properties;
              }
          }
          catch (IOException ioe)
          {
              throw new WrappedRuntimeException(
                  Utils.messages.createMessage(
                      MsgKey.ER_COULD_NOT_LOAD_METHOD_PROPERTY,
                      new Object[] { fileName, method }),
                  ioe);
          }
          // wrap these cached defaultProperties in a new Property object just so
          // that the caller of this method can't modify the default values
          return new Properties(defaultProperties);
      }
  
      /**
       * Load the properties file from a resource stream.  If a
       * key name such as "org.apache.xslt.xxx", fix up the start of
       * string to be a curly namespace.  If a key name starts with
       * "xslt.output.xxx", clip off "xslt.output.".  If a key name *or* a
       * key value is discovered, check for \u003a in the text, and
       * fix it up to be ":", since earlier versions of the JDK do not
       * handle the escape sequence (at least in key names).
       *
       * @param resourceName non-null reference to resource name.
       * @param defaults Default properties, which may be null.
       */
      static private Properties loadPropertiesFile(
          final String resourceName,
          Properties defaults)
          throws IOException
      {
  
          // This static method should eventually be moved to a thread-specific class
          // so that we can cache the ContextClassLoader and bottleneck all properties file
          // loading throughout Xalan.
  
          Properties props = new Properties(defaults);
  
          InputStream is = null;
          BufferedInputStream bis = null;
  
          try
          {
              if (ACCESS_CONTROLLER_CLASS != null)
              {
                  is = (InputStream) AccessController
                      .doPrivileged(new PrivilegedAction() {
                          public Object run()
                          {
                              return OutputPropertiesFactory.class
                                  .getResourceAsStream(resourceName);
                          }
                      });
              }
              else
              {
                  // User may be using older JDK ( JDK < 1.2 )
                  is = OutputPropertiesFactory.class
                      .getResourceAsStream(resourceName);
              }
  
              bis = new BufferedInputStream(is);
              props.load(bis);
          }
          catch (IOException ioe)
          {
              if (defaults == null)
              {
                  throw ioe;
              }
              else
              {
                  throw new WrappedRuntimeException(
                      Utils.messages.createMessage(
                          MsgKey.ER_COULD_NOT_LOAD_RESOURCE,
                          new Object[] { resourceName }),
                      ioe);
                  //"Could not load '"+resourceName+"' (check CLASSPATH), now using just the defaults ", ioe);
              }
          }
          catch (SecurityException se)
          {
              // Repeat IOException handling for sandbox/applet case -sc
              if (defaults == null)
              {
                  throw se;
              }
              else
              {
                  throw new WrappedRuntimeException(
                      Utils.messages.createMessage(
                          MsgKey.ER_COULD_NOT_LOAD_RESOURCE,
                          new Object[] { resourceName }),
                      se);
                  //"Could not load '"+resourceName+"' (check CLASSPATH, applet security), now using just the defaults ", se);
              }
          }
          finally
          {
              if (bis != null)
              {
                  bis.close();
              }
              if (is != null)
              {
                  is.close();
              }
          }
  
          // Note that we're working at the HashTable level here,
          // and not at the Properties level!  This is important
          // because we don't want to modify the default properties.
          // NB: If fixupPropertyString ends up changing the property
          // name or value, we need to remove the old key and re-add
          // with the new key and value.  However, then our Enumeration
          // could lose its place in the HashTable.  So, we first
          // clone the HashTable and enumerate over that since the
          // clone will not change.  When we migrate to Collections,
          // this code should be revisited and cleaned up to use
          // an Iterator which may (or may not) alleviate the need for
          // the clone.  Many thanks to Padraig O'hIceadha
          // <padraig@gradient.ie> for finding this problem.  Bugzilla 2000.
  
          Enumeration keys = ((Properties) props.clone()).keys();
          while (keys.hasMoreElements())
          {
              String key = (String) keys.nextElement();
              // Now check if the given key was specified as a
              // System property. If so, the system property
              // overides the default value in the propery file.
              String value = null;
              try
              {
                  value = System.getProperty(key);
              }
              catch (SecurityException se)
              {
                  // No-op for sandbox/applet case, leave null -sc
              }
              if (value == null)
                  value = (String) props.get(key);
  
              String newKey = fixupPropertyString(key, true);
              String newValue = null;
              try
              {
                  newValue = System.getProperty(newKey);
              }
              catch (SecurityException se)
              {
                  // No-op for sandbox/applet case, leave null -sc
              }
              if (newValue == null)
                  newValue = fixupPropertyString(value, false);
              else
                  newValue = fixupPropertyString(newValue, false);
  
              if (key != newKey || value != newValue)
              {
                  props.remove(key);
                  props.put(newKey, newValue);
              }
  
          }
  
          return props;
      }
  
      /**
       * Fix up a string in an output properties file according to
       * the rules of {@link #loadPropertiesFile}.
       *
       * @param s non-null reference to string that may need to be fixed up.
       * @return A new string if fixup occured, otherwise the s argument.
       */
      static private String fixupPropertyString(String s, boolean doClipping)
      {
          int index;
          if (doClipping && s.startsWith(S_XSLT_PREFIX))
          {
              s = s.substring(S_XSLT_PREFIX_LEN);
          }
          if (s.startsWith(S_XALAN_PREFIX))
          {
              s =
                  S_BUILTIN_EXTENSIONS_UNIVERSAL
                      + s.substring(S_XALAN_PREFIX_LEN);
          }
          if ((index = s.indexOf("\\u003a")) > 0)
          {
              String temp = s.substring(index + 6);
              s = s.substring(0, index) + ":" + temp;
  
          }
          return s;
      }
  
  }
  